Staff invitation email delivery
An owner chooses Email invitation in Team management. The backend creates the invitation and its email outbox row in one transaction. The API returns only a queued receipt. Create private link remains available and does not send email.
Configuration
Apply 20261011002700_staff_invitation_delivery_outbox.sql after the staff invitation acceptance migration. Deploy the deliver-staff-invitation Supabase Edge Function. Set these secrets before enabling the backend scheduler:
| Runtime | Setting | Purpose |
|---|---|---|
| Backend and Edge Function | STAFF_INVITATION_DELIVERY_KEY | The same canonical base64 encoding of 32 random bytes. Encrypts invitation credentials at rest. Back up and rotate through a reviewed migration; old queued ciphertext needs the old key until drained. |
| Backend and Edge Function | STAFF_INVITATION_WORKER_SECRET | Shared strong random secret for scheduler calls. |
| Backend | STAFF_INVITATION_DELIVERY_ENABLED=true | Starts the 30-second scheduler. Leave unset until the migration and Edge Function are ready. |
| Backend | SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY | Existing service configuration used to call the worker. |
| Edge Function | SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY | Claims and records durable delivery receipts. |
| Edge Function | ADMIN_URL | HTTPS admin origin, with no path or query, for the invitation link. |
| Edge Function | SES_REGION, SES_ACCESS_KEY_ID, SES_SECRET_ACCESS_KEY, EMAIL_FROM | Configured Amazon SES sender. EMAIL_REPLY_TO and SES_CONFIGURATION_SET are optional. |
The Edge Function refuses to claim work if any required setting is missing. The backend refuses new email invitations if the encryption key is missing. Secrets must not be logged or placed in the admin app.
States and recovery
queued means durable request only. sending means a worker has claimed it. provider_accepted means SES returned a message ID; it does not prove inbox delivery. failed means three explicit throttling rejections were exhausted. unknown means a provider call might have happened or a claim timed out; it is never retried automatically. cancelled means the invitation was no longer pending, had expired, or its immutable offer no longer matched at claim time.
Owners can check the receipt from the invitation list. For unknown, inspect SES message events and the outbox row before deciding whether to revoke the invitation and issue a new one. Do not manually reset unknown to queued: that can duplicate an email. A revoked link cannot be accepted, including when SES accepted an email just before revocation.
Rollback: turn off STAFF_INVITATION_DELIVERY_ENABLED to stop new worker calls. Keep the outbox, claims, audit receipts, and encryption key until queued and unknown work has been reconciled. The private-link workflow remains available.
Local verification uses scripts/db-access-tests.mjs with the authoritative access, staff access command, invitation acceptance, and delivery migrations in that order, plus the backend staffInvitationDelivery Jest suite. No live invitation should be created as part of automated verification.