diff --git a/README.md b/README.md index b343cdb..1775248 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ decision rules. - [architecture](docs/architecture.md) - [account security](docs/account-security.md) +- [comail](docs/comail.md) - [development](docs/development.md) - [operator guide](docs/operations.md) - [invite codes](docs/invite-codes.md) diff --git a/docs/comail.md b/docs/comail.md new file mode 100644 index 0000000..402e23b --- /dev/null +++ b/docs/comail.md @@ -0,0 +1,114 @@ +# comail + +ZDS uses [Comail](https://comail.at/) as its default email delivery provider. +Comail is cooperative email infrastructure for atproto identities: a sending +domain is enrolled under an atproto DID, and requests to send mail authenticate +with both an API key and that DID. + +ZDS uses Comail for account and identity email tokens: + +- account email confirmation +- email update confirmation +- PLC operation confirmation + +These emails are user-triggered transactional messages. ZDS sends them through +Comail's HTTP Send API with `category: "verification"`, which tells Comail the +message is part of an auth or verification flow rather than broadcast mail. + +## identity model + +ZDS keeps the PDS service identity separate from the Comail sending identity. + +- `ZDS_SERVER_DID` is the PDS service DID, usually `did:web:`. +- `ZDS_COMAIL_DID` is the atproto DID that enrolled the sending domain with + Comail. +- `ZDS_EMAIL_FROM` is the bare sender address on that enrolled domain. + +For the hosted `pds.zat.dev` deployment: + +```sh +ZDS_MAIL_PROVIDER=comail +ZDS_COMAIL_DID=did:plc:mkqt76xvfgxuemlwlx6ruc3w +ZDS_EMAIL_FROM=noreply@zat.dev +``` + +That means mail is sent as `noreply@zat.dev`, authorized by the `zat.dev` +account DID. It does not mean mail is sent by `did:web:pds.zat.dev`. + +## configuration + +The Comail provider uses these ZDS settings: + +```sh +ZDS_MAIL_PROVIDER=comail +ZDS_COMAIL_API_KEY=... +ZDS_COMAIL_DID=did:plc:... +ZDS_EMAIL_FROM=noreply@example.com +``` + +`ZDS_EMAIL_FROM` must be a bare email address. Comail rejects display-name +forms such as: + +```sh +ZDS_EMAIL_FROM='ZDS ' +``` + +That form is accepted by some providers, including Resend, but Comail returns +`INVALID_REQUEST`. + +## request shape + +All provider-specific behavior stays in `src/core/mail.zig`. The rest of ZDS +calls `mail.sendCode(...)` and does not know which provider is active. + +For Comail, ZDS sends: + +```http +POST https://smtp.atmos.email/v1/send +Authorization: Bearer +X-Atmos-DID: +Content-Type: application/json +``` + +with JSON shaped like: + +```json +{ + "from": "noreply@example.com", + "to": "user@example.com", + "subject": "Confirm email", + "text": "Hello handle. Your email confirmation code is ABCDE-FGHIJ. This code will expire in ten minutes.", + "category": "verification" +} +``` + +ZDS does not log successful message bodies or tokens in production. If Comail +returns a non-2xx response, ZDS logs the provider, status code, and a short +response excerpt so operators can diagnose configuration or delivery issues. + +## local development + +Local development can run without Comail secrets. When `ZDS_PUBLIC_URL` is a +localhost URL and mail config is incomplete, ZDS logs the generated code instead +of sending mail. + +For non-local deployments, incomplete mail config is a hard failure. This is +intentional: a production PDS should not tell clients that verification mail was +sent when the provider was not configured. + +## operational notes + +- The sending domain must be enrolled in Comail before production delivery will + work. +- New domains start in Comail's warming tiers. Verification emails are low + volume, but repeated manual retries can still consume the early hourly/daily + allowance. +- Keep `ZDS_COMAIL_API_KEY` in deployment secrets, not in committed files. +- If Bluesky reports "Failed to send email", check Fly logs for + `mail delivery failed provider=comail`. + +Useful Comail docs: + +- [HTTP Send API](https://comail.at/docs/send-api) +- [DNS records](https://comail.at/docs/dns) +- [Warming tiers](https://comail.at/docs/warming) diff --git a/docs/operations.md b/docs/operations.md index 062c78e..4764acd 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -90,6 +90,9 @@ Common deployment settings: sends it as `X-Atmos-DID`. - `ZDS_RESEND_API_KEY`: Resend API key, used only when `ZDS_MAIL_PROVIDER=resend`. + +See [comail](comail.md) for the default provider's identity model, request +shape, and production troubleshooting notes. - `ZDS_BLOB_UPLOAD_LIMIT`: upload body limit. Default: `100000000`. - `ZDS_BLOBSTORE_PATH`: disk blobstore root. - `ZDS_CRAWLERS`: comma-separated relay crawl targets.