fix(ses): let self-hosters use SNS emulators and the log-only switch

KM
3d ago
+97-1

Follow-up to #466 for self-hosted installs. Cloud behaviour is unchanged.

What changed

  • Emulator subscriptions confirm again. #466 always rejected a SubscriptionConfirmation whose SubscribeURL isn't an AWS SNS host, even in log-only mode and in development. SNS emulators send their own URL (local-ses-sns sends http://localhost:<port>), so with an emulator the subscription never confirmed and no delivery events arrived. This also broke the local dev setup in docker/dev. A non-AWS SubscribeURL is now accepted only when both AWS_SNS_ENDPOINT is set and signature enforcement is off (NODE_ENV=development or SNS_SIGNATURE_VERIFICATION=log-only). Real AWS setups, including ones using AWS_SNS_ENDPOINT for a VPC endpoint, keep the strict AWS-host check. The fetch still refuses redirects.
  • SNS_SIGNATURE_VERIFICATION reaches Docker Compose installs. docker/prod/compose.yml lists every variable explicitly and didn't pass this one, so the escape hatch from #466 did nothing for Compose users. It now defaults to enforce.
  • Documented in .env.selfhost.example and the self-hosting overview (Step 3), including what to look for in the logs if statuses stop moving past Sent.

Trade-off

In the emulator mode (custom endpoint + log-only), someone who knows the topic ARN could make the server send one GET to a URL of their choosing. That mode is explicitly opted into and already accepts unsigned events, so this is no wider than what the operator chose.

How I checked it

  • New route tests: the emulator SubscribeURL is confirmed with AWS_SNS_ENDPOINT + log-only; rejected when signatures are enforced even with AWS_SNS_ENDPOINT set; rejected in log-only without AWS_SNS_ENDPOINT.
  • vitest -c vitest.api.config.ts src/app/api/ses_callback: 14/14. vitest -c vitest.unit.config.ts src/server/aws: 39/39. pnpm --filter=web typecheck: clean.
  • local-ses-sns's SubscribeURL format checked in usesend/local-ses-sns src/index.ts.

Summary by cubic

Fixes self-hosted SNS emulator setups so subscription confirmations work again, and makes the log-only signature switch effective for Docker Compose installs. Cloud behavior is unchanged.

  • #466 always rejected a SubscriptionConfirmation whose SubscribeURL isn't on an AWS SNS host, even when signature verification was off, so SNS emulators could never confirm and delivery events never arrived.
  • A non-AWS SubscribeURL is now accepted only when both AWS_SNS_ENDPOINT is set and signature enforcement is off (development or SNS_SIGNATURE_VERIFICATION=log-only); real AWS setups keep the strict AWS-host check, and fetch still refuses redirects.
  • docker/prod/compose.yml now passes SNS_SIGNATURE_VERIFICATION (default enforce) — it previously wasn't listed, so the switch had no effect for Compose users.
  • Documented in .env.selfhost.example and the self-hosting overview (Step 3), including what to look for in the logs if statuses stop moving past Sent.
  • Trade-off: with a custom endpoint plus log-only, anyone who knows the topic ARN could make the server send one GET to a URL of their choosing; that mode already accepts unsigned events and requires explicit opt-in.

Summary by CodeRabbit

  • New Features
    • Self-hosted deployments can configure SNS signature verification. Verification is enforced by default; log-only accepts and logs messages with invalid signatures.
    • When using an SNS emulator such as LocalStack, a custom endpoint can allow subscription confirmation URLs during log-only mode.
  • Documentation
    • Self-hosting guidance now explains signature verification, how to investigate rejected SNS messages, and the settings relevant to emulator use.