Refine workspaces and event publishing; harden uploads and email delivery
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Local verification — September 9, 2026
|
||||
|
||||
- `bun run check`: unit tests, typecheck, lint and production build passed.
|
||||
- Clean-database migration check passed, including the webhook inbox migration.
|
||||
- Opt-in publishing integration passed with real storage/worker processing:
|
||||
note-only and email-only guests, approval reset, hidden statistics, scheduled
|
||||
publication and note/gallery visibility, submission opening, gallery policies,
|
||||
unauthorized upload completion rejection, concurrent completion deduplication,
|
||||
direct presigned upload, smaller display/thumbnail outputs, and closed/draft
|
||||
rejection. Source: one existing demo original, left untouched.
|
||||
- Mailpit integration passed: opt-in only, concurrent enqueue deduplication,
|
||||
provider acceptance and no repeat notification.
|
||||
- Webhook tests passed: valid/invalid/stale signatures, ignored event types,
|
||||
duplicate and early callbacks, out-of-order terminal outcomes, event-scoped
|
||||
query and provider separation. No production Resend requests were sent.
|
||||
- Browser: guest form checked at 390×844 with no horizontal overflow or console
|
||||
warnings/errors; organizer People tab loaded delivery history successfully.
|
||||
Temporary viewport override was reset.
|
||||
|
||||
Temporary test events and uploaded copies were removed by fixture cleanup.
|
||||
Mailpit test messages remain available. These checks do not replace real-device
|
||||
camera/HEIC testing or production deliverability verification. Resend webhook
|
||||
registration and its signing secret are deployment configuration, not activated
|
||||
by local tests. See `email-delivery.md`.
|
||||
Reference in New Issue
Block a user