82 lines
4.3 KiB
Markdown
82 lines
4.3 KiB
Markdown
# Email delivery
|
|
|
|
Manyangles uses the RaceTix-style branded shell for verification, password resets,
|
|
account invitations, and gallery-ready messages. HTML has plain-text alternatives,
|
|
escaped dynamic content, and system-font fallbacks. People previews the actual
|
|
gallery-ready HTML before queueing.
|
|
|
|
## Production Resend configuration
|
|
|
|
Set `EMAIL_PROVIDER=resend`, `RESEND_API_KEY`, `EMAIL_FROM` (a verified sender), and
|
|
`NEXT_PUBLIC_APP_URL` (the public HTTPS origin). Run the image/email worker with
|
|
the same environment as the web process. Configuration changes do not migrate
|
|
pending emails between providers. No production test sends are needed.
|
|
|
|
## Completion outbox
|
|
|
|
Organizer actions enqueue one immutable payload per event/normalized email
|
|
address. A unique database constraint deduplicates concurrent requests. Only
|
|
opted-in guests qualify. Before sending, the worker checks consent and gallery
|
|
visibility again. Messages whose guest link changed are held for review.
|
|
|
|
The worker records provider acceptance, attempts and sanitized errors. “Sent”
|
|
means accepted by Resend/SMTP, not delivered to an inbox. No automatic event
|
|
schedule sends email.
|
|
|
|
Configure a Resend webhook at `/api/webhooks/resend` and set
|
|
`RESEND_WEBHOOK_SECRET` in the web process. Subscribe to `email.sent`,
|
|
`email.delivered`, `email.delivery_delayed`, `email.bounced`, `email.complained`,
|
|
and `email.failed`. The endpoint verifies the bounded raw body with the Resend
|
|
SDK and deduplicates by signed `svix-id`. It stores only message identifiers,
|
|
outcomes and timestamps, never raw recipient payloads. Failed database writes
|
|
return 503 for provider retry. Callbacks arriving before the worker saves its
|
|
provider reference are retained and matched when email history is read.
|
|
|
|
People shows provider outcomes separately from queue status. Complaints,
|
|
bounces and failures take precedence over delivered/delayed/sent regardless of
|
|
callback order. Delivered means acceptance by the recipient mail server, not
|
|
inbox placement. This tracks gallery-ready outbox messages; auth email outcomes
|
|
do not have a management UI. Tracking does not change guest consent or implement
|
|
a cross-event suppression list. Mailpit does not produce Resend callbacks.
|
|
|
|
Resend retries reuse `gallery-ready/<delivery-id>` and the original payload.
|
|
Retries back off, stop after five attempts, and stay inside a conservative
|
|
23-hour window (Resend retains idempotency keys for 24 hours). Expired/uncertain
|
|
deliveries require provider review. The People tab permits safe Resend retries
|
|
inside that window. SMTP ambiguity is not automatically retried.
|
|
|
|
Account verification, password resets and account invitations remain synchronous
|
|
transactional sends. They also use stable, recipient-specific idempotency keys.
|
|
|
|
## Local verification
|
|
|
|
Keep `EMAIL_PROVIDER=mailpit`, `SMTP_HOST=127.0.0.1`, `SMTP_PORT=1027`.
|
|
Open Mailpit at http://localhost:8027. Test scripts reject nonlocal Mailpit settings.
|
|
|
|
`bun run email:preview` sends all four templates to the local inbox, like RaceTix's
|
|
preview workflow. Authentication/invitation links in these samples are deliberately
|
|
nonfunctional; the gallery sample links to `/e/demo`.
|
|
|
|
Docker Compose already supplies Postgres (5439), Mailpit (SMTP 1027 / inbox 8027),
|
|
and Garage (3900 / 3903). Use `bun run docker:up`, `bun run docker:logs`, and
|
|
`bun run docker:down`. The web app and worker run on the host with Bun, just as
|
|
RaceTix's development setup does. Stopping this project's Compose stack does not
|
|
stop Colima or disrupt other projects.
|
|
|
|
```sh
|
|
bun run typecheck
|
|
bun test
|
|
WEBHOOK_INTEGRATION=1 bun --env-file=.env test packages/email/src/webhooks.test.ts
|
|
cd apps/web
|
|
NOTIFICATIONS_INTEGRATION=1 PUBLISHING_INTEGRATION=1 bun --env-file=../../.env test src/server/guest-notifications.integration.test.ts src/server/publishing.integration.test.ts
|
|
```
|
|
|
|
With the local worker running, add `REAL_UPLOAD_INTEGRATION=1` to the publishing
|
|
test command to copy one existing `/e/demo` original through a presigned PUT,
|
|
verify a single queued job and smaller generated variants, then remove only the
|
|
temporary event and its uploaded objects. The original demo assets are untouched.
|
|
|
|
Run `bun --env-file=.env packages/database/src/verify-migrations.ts` from the repo
|
|
root to migrate a temporary local database; the script removes that database
|
|
after verification. It never migrates a remote database.
|