Pharos
Guides.md

Sending & deliverability

Pharos drives your email; it does not send it on your behalf. You connect your own provider — Resend or Amazon SES — and mail leaves through your account, under your domain, on your reputation.

That is deliberate. Pooling every customer's mail through one shared sending account means one customer's bad campaign degrades everyone else's delivery, and there is no way to give the affected customers their reputation back. Your credential, your reputation, your control.

Connect a sending account

Sending accounts are managed in the dashboard under Settings → Sending. They are workspace-scoped, not per-project: one Resend account covers every app you ship, and each project chooses which account it sends through.

Only workspace owners and admins can add, edit or remove a credential — and the same restriction covers attaching one to a project, because deciding which credential a project's mail leaves through is the same authority as holding the credential.

Resend

You need an API key (re_…) from your Resend dashboard, and a verified sending domain there.

Amazon SES

You need an access key id, a secret access key, and the region your identity lives in. The from-address on each project must be a verified identity in that SES account, and the account must be out of the SES sandbox before it can mail addresses you have not verified.

Credentials are encrypted at rest and never returned by any endpoint — a response tells you whether a credential is set, never what it is. There is no way to read one back, only to replace it. Rotation is therefore always replace-then-verify, and replacing a credential clears whatever Pharos had recorded about the old one.

Point a project at an account

A project sends through exactly one account:

curl -X PATCH https://pharosbase.com/api/projects \
  -H "Content-Type: application/json" \
  -d '{ "slug": "acme-app", "emailAccountId": "eac_…" }'

Session-authenticated, owners and admins only. A project with no account attached refuses to send rather than falling back to somebody else's credential — the error names the project and says what is missing.

You cannot delete an account while a project still points at it; that returns 409. Detach the projects first, so that removing a credential is never a silent way to break delivery.

Delivery tracking

Sending works as soon as the credential does. Knowing what happened to a message — delivered, bounced, opened, marked as spam — needs one more step, and it differs by provider.

Resend

Resend signs webhooks with a per-endpoint secret, so Pharos gives you an endpoint URL and you give it back a signing secret.

  1. Connect the account. Pharos shows the endpoint URL for it.
  2. Add that URL as a webhook in your Resend dashboard, subscribed to the delivery, bounce, complaint, open and click events.
  3. Paste the whsec_… signing secret Resend hands you back into the account's Add signing secret field.

Until that secret is set, mail sends normally and every send stays at sent — opens, bounces and complaints are simply never recorded. The settings page says so on the account rather than leaving you to work it out from an empty Sends view.

Amazon SES

Delivery tracking is not yet available for your own SES account. Mail sends normally; every send stays at sent, and bounces and complaints are not recorded against it.

The reason is worth stating rather than hiding. SES reports delivery through SNS, and an SNS signature proves that AWS sent a message — not which AWS account's topic it came from. A single shared endpoint therefore could not tell your notifications from another customer's, so wiring one up would mean accepting delivery events for your mail from anybody with an AWS account. SES needs its own per-account endpoint, the way Resend has one, and that is not built yet.

If delivery tracking matters more to you than staying on SES, Resend is fully wired today.

Pharos also does not name an SES configuration set on sends from your own credential. A configuration set is account-local, and naming one that does not exist in your account makes SES reject every message.

What tracking buys you

Every send is one row in the project's send log, whatever produced it — a campaign, an automation, or a transactional event. Delivery events move that row along:

queued → sent → delivered
                    ↳ bounced
                    ↳ complained

Read it with GET /api/sends or the Sends view in the dashboard.

Two of those outcomes do more than record themselves. A permanent bounce — an address that does not exist — and a spam complaint both unsubscribe the contact across the whole project immediately, on either provider. See Unsubscribes & suppression for what that protects and why it cannot be undone by a sync.

Before your first campaign

  • Verify your domain with your provider, and set the project's from-address to an identity that domain covers.
  • Set the project's mailing address. A physical address in the footer is required by bulk-email rules in most jurisdictions, and it is a project field rather than a template one so you cannot forget it per-template.
  • Configure the webhook — see above. Without it you are sending blind.
  • Send yourself a test with recipientMode: "test:you@example.com" on POST /api/send. It delivers one real email and creates no campaign, so it exercises the credential end to end.
  • Check {{unsubscribeUrl}} is in the template. Mail to a list without a working unsubscribe is the fastest route to a complaint.