Repository navigation
feat(email): send through resend instead of postmark - #23
Conversation
|
| if (!emailEnv.RESEND_API_KEY) { | ||
| this.logSkipped(messages); | ||
| return messages.map((message) => ({ to: message.to, errorCode: 0, message: 'Email sending disabled' })); |
There was a problem hiding this comment.
Preserve sending during the legacy-secret migration · correctness
When this release runs with only POSTMARK_SERVER_TOKEN, as the description's deployment sequence specifies, this branch skips sending and returns success for every recipient. AllowlistService.sendInviteEmails then persists inviteEmailSentAt for those unsent invites (allowlist.service.ts:97–109), so installing the Resend secret later does not make them retryable. Production does not keep sending through Postmark after this code is deployed: the old provider has been removed completely.
Suggested fix
Retain a temporary legacy Postmark fallback when only the old token is configured, until the secret migration is complete. Keep log-and-skip only for environments with neither provider configured, and document that migration order.
Prompt for a coding agent
In packages/common/src/email/email.repository.ts and packages/common/src/email/env.ts, retain a temporary Postmark compatibility path when POSTMARK_SERVER_TOKEN is configured but RESEND_API_KEY is absent. Use the Resend path when its key is present, and only use the existing log-and-skip behavior when neither provider is configured. Document the secret migration order in docs/email.md and add a regression test for an environment containing only the legacy token.
Moves transactional email (today: the invite flow in yucca-admin-api) from Postmark to Resend. FUTO's Resend account, sending domains and per-consumer keys are managed in immich-app/core-infra-tf (
resend/futo-account); this app's key isYUCCA_RESEND_API_KEY, asending_accesskey restricted tofuto.cloud, which matches the prod from-addressnoreply@futo.cloud.Client (
@common/server/email)POST /emailsandPOST /emails/batchwithAuthorization: Bearer, instead of Postmark's/email,/email/batchandX-Postmark-Server-Token.errorCode= the HTTP status), while earlier chunks keep their results. The invite flow then stamps only the delivered entries, so a retry can't resend invites that already went out.category=<tag>. The Postmark-onlyMessageStreamis dropped.RESEND_API_KEY(unset → log and skip, as before) andRESEND_API_URL(defaulthttps://api.resend.com), replacingPOSTMARK_SERVER_TOKEN/POSTMARK_API_URL.Dev mock:
mock-postmark-provideris renamed tomock-resend-providerand speaks Resend's API, still delivering into Mailpit. Compose, Tilt, the dev charts and HelmReleases,build-check.yml,.env.exampleand the admin-api dev env defaults are updated to match, and so aredocs/email.mdandCLAUDE.md.Deploying: the infrastructure repo has to pass
RESEND_API_KEYin the yucca-admin-api Secret instead ofPOSTMARK_SERVER_TOKEN. That's a follow-up PR in immich-app/yucca, to merge once a release containing this one is running in prod. Until then, prod keeps sending through Postmark.Tested locally:
email.repository.spec.tsdrives the real repository against a mockedfetch: Resend paths, the bearer key, payload mapping and tags, 100-email chunking, and a rejected second chunk keeping the first 100 as sent;allowlist.service.spec.tspasses;@common/servertype-checks;Not tested against the real Resend API.
main