···11+# comail
22+33+ZDS uses [Comail](https://comail.at/) as its default email delivery provider.
44+Comail is cooperative email infrastructure for atproto identities: a sending
55+domain is enrolled under an atproto DID, and requests to send mail authenticate
66+with both an API key and that DID.
77+88+ZDS uses Comail for account and identity email tokens:
99+1010+- account email confirmation
1111+- email update confirmation
1212+- PLC operation confirmation
1313+1414+These emails are user-triggered transactional messages. ZDS sends them through
1515+Comail's HTTP Send API with `category: "verification"`, which tells Comail the
1616+message is part of an auth or verification flow rather than broadcast mail.
1717+1818+## identity model
1919+2020+ZDS keeps the PDS service identity separate from the Comail sending identity.
2121+2222+- `ZDS_SERVER_DID` is the PDS service DID, usually `did:web:<pds-host>`.
2323+- `ZDS_COMAIL_DID` is the atproto DID that enrolled the sending domain with
2424+ Comail.
2525+- `ZDS_EMAIL_FROM` is the bare sender address on that enrolled domain.
2626+2727+For the hosted `pds.zat.dev` deployment:
2828+2929+```sh
3030+ZDS_MAIL_PROVIDER=comail
3131+ZDS_COMAIL_DID=did:plc:mkqt76xvfgxuemlwlx6ruc3w
3232+ZDS_EMAIL_FROM=noreply@zat.dev
3333+```
3434+3535+That means mail is sent as `noreply@zat.dev`, authorized by the `zat.dev`
3636+account DID. It does not mean mail is sent by `did:web:pds.zat.dev`.
3737+3838+## configuration
3939+4040+The Comail provider uses these ZDS settings:
4141+4242+```sh
4343+ZDS_MAIL_PROVIDER=comail
4444+ZDS_COMAIL_API_KEY=...
4545+ZDS_COMAIL_DID=did:plc:...
4646+ZDS_EMAIL_FROM=noreply@example.com
4747+```
4848+4949+`ZDS_EMAIL_FROM` must be a bare email address. Comail rejects display-name
5050+forms such as:
5151+5252+```sh
5353+ZDS_EMAIL_FROM='ZDS <noreply@example.com>'
5454+```
5555+5656+That form is accepted by some providers, including Resend, but Comail returns
5757+`INVALID_REQUEST`.
5858+5959+## request shape
6060+6161+All provider-specific behavior stays in `src/core/mail.zig`. The rest of ZDS
6262+calls `mail.sendCode(...)` and does not know which provider is active.
6363+6464+For Comail, ZDS sends:
6565+6666+```http
6767+POST https://smtp.atmos.email/v1/send
6868+Authorization: Bearer <ZDS_COMAIL_API_KEY>
6969+X-Atmos-DID: <ZDS_COMAIL_DID>
7070+Content-Type: application/json
7171+```
7272+7373+with JSON shaped like:
7474+7575+```json
7676+{
7777+ "from": "noreply@example.com",
7878+ "to": "user@example.com",
7979+ "subject": "Confirm email",
8080+ "text": "Hello handle. Your email confirmation code is ABCDE-FGHIJ. This code will expire in ten minutes.",
8181+ "category": "verification"
8282+}
8383+```
8484+8585+ZDS does not log successful message bodies or tokens in production. If Comail
8686+returns a non-2xx response, ZDS logs the provider, status code, and a short
8787+response excerpt so operators can diagnose configuration or delivery issues.
8888+8989+## local development
9090+9191+Local development can run without Comail secrets. When `ZDS_PUBLIC_URL` is a
9292+localhost URL and mail config is incomplete, ZDS logs the generated code instead
9393+of sending mail.
9494+9595+For non-local deployments, incomplete mail config is a hard failure. This is
9696+intentional: a production PDS should not tell clients that verification mail was
9797+sent when the provider was not configured.
9898+9999+## operational notes
100100+101101+- The sending domain must be enrolled in Comail before production delivery will
102102+ work.
103103+- New domains start in Comail's warming tiers. Verification emails are low
104104+ volume, but repeated manual retries can still consume the early hourly/daily
105105+ allowance.
106106+- Keep `ZDS_COMAIL_API_KEY` in deployment secrets, not in committed files.
107107+- If Bluesky reports "Failed to send email", check Fly logs for
108108+ `mail delivery failed provider=comail`.
109109+110110+Useful Comail docs:
111111+112112+- [HTTP Send API](https://comail.at/docs/send-api)
113113+- [DNS records](https://comail.at/docs/dns)
114114+- [Warming tiers](https://comail.at/docs/warming)
+3
docs/operations.md
···9090 sends it as `X-Atmos-DID`.
9191- `ZDS_RESEND_API_KEY`: Resend API key, used only when
9292 `ZDS_MAIL_PROVIDER=resend`.
9393+9494+See [comail](comail.md) for the default provider's identity model, request
9595+shape, and production troubleshooting notes.
9396- `ZDS_BLOB_UPLOAD_LIMIT`: upload body limit. Default: `100000000`.
9497- `ZDS_BLOBSTORE_PATH`: disk blobstore root.
9598- `ZDS_CRAWLERS`: comma-separated relay crawl targets.