# 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:

```bash
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:

```text
queued → sent → delivered
                    ↳ bounced
                    ↳ complained
```

Read it with [`GET /api/sends`](/docs/api-campaigns#get-apisends) 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](/docs/compliance) 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`](/docs/api-campaigns#post-apisend). 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.
