feat(email): add Postmark send provider#164
Conversation
Extract the single ~360-line email-sender.ts into a focused folder: shared types (types.ts), helpers (shared.ts), one class per provider under providers/, and the createEmailSender factory + re-export surface in index.ts. The ./email-sender import path is unchanged (resolves to index.ts), so no callers change. Pure code move, no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add Postmark as an outbound provider alongside Cloudflare, Resend, and Bavimail. PostmarkSender posts to https://api.postmarkapp.com/email with an X-Postmark-Server-Token header; Reply-To maps to Postmark's ReplyTo field and other headers go through the Headers array. Selected via POSTMARK_API_KEY, with runtime precedence Bavimail > Postmark > Resend > Cloudflare Email Sending. Docs (README provider matrix/prereqs/secrets, wrangler + dev.vars examples, onboarding skill) and CHANGELOG updated to cover Postmark everywhere the other providers appear. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ocation The global `fetch` invoked as a method reference (`this.fetchFn(...)`) throws "Illegal invocation" in the Cloudflare Workers runtime. Because every test injects its own fetch, the unbound default only ran in production — where it silently turned every send into a failed result (nothing reached Postmark). Bind the default to globalThis and log the failure (matching CloudflareSender). No unit test for the un-injected default path: exercising it makes a real outbound fetch, which the vitest-pool-workers runtime blocks with a "Network connection lost" rejection that leaks across isolates and fails unrelated tests. A code comment documents the binding requirement. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
77e402a to
7a6a8e5
Compare
|
Heads-up while this is fresh: We hit exactly this with Postmark in production — every send silently failed before reaching the API. I've deliberately left |
|
I've been running this in one of my three production instances for the 4 days and it's working nicely. |
What & why
Adds Postmark as an outbound email provider alongside Cloudflare Email Sending, Resend, and Bavimail.
PostmarkSenderPOSTs tohttps://api.postmarkapp.com/emailwith anX-Postmark-Server-Tokenheader. It mapsReply-Toto Postmark's dedicatedReplyTofield and routes any other headers through theHeadersarray, base64-encodes attachments intoAttachments, and treats both non-2xx responses and200 + non-zero ErrorCodeas failures. It's fetch-based with an injectablefetchFn(so tests never touchglobalThis.fetch), mirroringBavimailSender.Selected via the
POSTMARK_API_KEYsecret. Runtime precedence: Bavimail > Postmark > Resend > Cloudflare Email Sending.Stacked on #163
Changes
worker/src/lib/email-sender/providers/postmark.ts— newPostmarkSender.types.tsunion +index.tsfactory branch & re-export.maxAttachmentBytes— appended toemail-sender.test.ts..dev.vars),wrangler.jsonc.example,.dev.vars.example, the onboarding skill (Option D), andCHANGELOG.md.Checklist
yarn tsc --noEmitcleanyarn test— 405 passed / 1 skipped (+11 Postmark tests)[Unreleased]