Skip to content

v1.1: deliverability, compliance, ergonomics#94

Merged
productdevbook merged 10 commits into
mainfrom
v1.1/deliverability-compliance
Apr 17, 2026
Merged

v1.1: deliverability, compliance, ergonomics#94
productdevbook merged 10 commits into
mainfrom
v1.1/deliverability-compliance

Conversation

@productdevbook
Copy link
Copy Markdown
Owner

@productdevbook productdevbook commented Apr 17, 2026

First batch of v1.1 work, built on top of the v1.0 driver base. Each commit closes 1-2 related issues so reviewers can track scope cleanly.

14 issues closed

Critical

High

Numbers

  • 10 commits, 14 issues closed
  • Tests: 161 → 214 (+53)
  • Zero runtime dependency additions
  • Zero breaking changes (every new field optional; new driver methods optional)

Follow-ups still open in v1.1

#62 (OAuth2 refresh), #63 (personalizations), #67 (typed getInstance), #69 (DMARC parser), #70 (unified events), #72 (package-split RFC), #73 (preheader + juice + dark-mode), #75 (per-driver rate limit), #76 (multi-region), #77 (queue adapters), #78 (ARC), #79 (MTA-STS), #80 (ARF), #81 (Address primitive), #82 (dedupe), #84 (preferences), #85 (SES inbound), #86 (metrics), #87 (handlebars/liquid/i18n), #90 (PII scrubber), #91 (AMP/DSN/raw), #92 (CID rewrite).

Test plan

  • pnpm test — 214/214 passing
  • pnpm typecheck — clean
  • pnpm fmt — clean
  • Zero dependency additions
  • SMTP DKIM signature round-trip verified against a freshly-generated RSA keypair
  • Unsubscribe token HMAC survives tamper, wrong-secret, and expiry probes
  • Suppression middleware exercised with error + drop policies
  • Queue deferred-send exercised against an injected clock

🤖 Generated with Claude Code

productdevbook and others added 6 commits April 17, 2026 08:30
- `msg.unsubscribe` auto-injects List-Unsubscribe + List-Unsubscribe-Post
  headers (Gmail/Yahoo Feb 2024 compliance).
- `unemail/compliance` ships sign/verify HMAC tokens + framework-agnostic
  one-click handler.
- `unemail/suppression` ships SuppressionStore interface + memory and
  unstorage-backed stores.
- `withSuppression` middleware blocks / drops suppressed recipients
  before driver.send.

Closes #57, #59.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…Result helpers

- Queue (memory + unstorage): honour msg.scheduledAt when deciding
  visibility; deferred jobs stay in the queue until their time comes.
- withRetry: new backoff strategies — exponential-jitter, full-jitter,
  decorrelated-jitter (AWS-style); new deadLetter option routes the
  exhausted message to a fallback driver with ctx.meta.deadLetterReason
  set.
- New unemail/result module — isOk/isErr/unwrap/unwrapOr/mapOk/mapErr/
  tryAsync helpers for the Result<T> union.

Closes #61, #83, #88.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- unemail/ics — zero-dep RFC 5545 VEVENT builder (REQUEST/PUBLISH/CANCEL/REPLY).
  Ships organizers, attendees with roles + partstat + rsvp, VALARMs,
  proper line folding and text escaping. Returns an Attachment the
  driver can forward natively.
- email.sendBatchStream(messages) — async iterable that yields one
  Result per message without short-circuiting on first error. Lets
  10k-recipient fan-outs run with constant memory.

Closes #64, #89.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tchers

- EmailDriver gains optional cancel(id) and retrieve(id) hooks gated
  by flags.cancelable / flags.retrievable. email.cancel(id) and
  email.retrieve(id) return UNSUPPORTED for drivers without them.
- Resend driver implements both, maps last_event → SendStatusState
  across scheduled/sent/delivered/bounced/opened/clicked/cancelled.
- unemail/test: new matchers toHaveSentTo, toHaveSentWithSubject,
  toHaveSentWithAttachment, toHaveSentMatching; plus
  toEmailSnapshot() helper that scrubs volatile headers for
  snapshot-testing.

Closes #65, #74.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- EmailMessage gains 3 new first-class fields:
  - tracking: { opens, clicks, unsubscribes } — per-message overrides
  - sandbox: true — test-send without delivery
  - metadata: Record<string, string> — echoed back by webhooks
- SendGrid driver maps them to tracking_settings, mail_settings.sandbox_mode,
  personalization custom_args.
- Mailgun maps to o:tracking-opens/-clicks, o:testmode, v:key.
- Postmark maps to TrackOpens, TrackLinks (HtmlAndText|None), Metadata.
- Resend X-Metadata-* headers.

Closes #66.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New first-class EmailMessage.template: { id, alias?, variables?, locale? }
mapped to each provider's native template API:

- SendGrid → template_id + personalization.dynamic_template_data
- Mailgun → template + h:X-Mailgun-Variables (JSON)
- Postmark → /email/withTemplate + TemplateId/TemplateAlias + TemplateModel
  (batch uses /email/batchWithTemplates when any message has a template)
- Brevo → templateId + params
- MailerSend → template_id + personalization[].data
- Loops → transactionalId + dataVariables (+ tags still merge in)
- Zeptomail → template_key / template_alias + merge_info
- Resend → unchanged (uses react/jsx pipeline)

Closes #60.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@productdevbook productdevbook added core Core API, types, middleware pipeline compliance RFC 8058, suppression, preferences, GDPR deliverability DKIM, ARC, DMARC, MTA-STS, BIMI v1.1 v1.1 milestone (shipped) priority-high Strong signal, high leverage labels Apr 17, 2026
productdevbook and others added 4 commits April 17, 2026 08:46
- New _smtp/dkim.ts — zero-dep DKIM signer, relaxed/relaxed
  canonicalization. Supports RFC 6376 (RSA-SHA256) and RFC 8463
  (Ed25519-SHA256). Pure Web Crypto, so the SMTP driver can still run
  on edge runtimes (given an SMTP-capable transport).
- SMTP driver accepts `dkim: DkimSignerOptions | (msg) => DkimSignerOptions | null`.
  Per-message resolver supports multi-tenant senders.
- Test generates a fresh RSA keypair, signs, parses the
  DKIM-Signature, and verifies it against the public key.

Closes #58.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- unemail/inbound/reply — stripReply(rawText) returns { text, quoted }.
  Heuristics for English, Turkish, German, French, Spanish header-
  bodied replies plus Outlook original-message blocks and '-- \n'
  signatures.
- unemail/inbound/thread — threadKey(parsedEmail) picks the canonical
  Message-ID (first References > In-Reply-To > Message-ID).
  buildThreads(batch) returns a Map grouping messages by root.

Closes #68.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rifier

- unemail/webhooks/standard — verifyStandardWebhook(request, options)
  and signStandardWebhook(secret, id, ts, body).
- Accepts webhook-id / webhook-timestamp / webhook-signature headers.
- Supports space-separated multi-version signatures (secret rotation).
- whsec_ prefix auto-stripped. Tolerance window defaults to 5 minutes.
- Pure Web Crypto, <5 kB gzipped vs Svix's ~1 MB.

Closes #71.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The inline `import("../types.ts").EmailMessage` in the DKIM function
type leaked to the generated `.d.mts` as a literal `.ts` path, which
Are The Types Wrong flagged as 'Internal resolution error'. Switching
to a top-level `import type` lets obuild rewrite it as `../types.mjs`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

compliance RFC 8058, suppression, preferences, GDPR core Core API, types, middleware pipeline deliverability DKIM, ARC, DMARC, MTA-STS, BIMI priority-high Strong signal, high leverage v1.1 v1.1 milestone (shipped)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant