feat: Define EmailSender interface and email message types - #152
Merged
Conversation
Add lib/email/types.ts, the zero-import shared contract for the email subsystem. Exports EmailSendResult (discriminated ok/error union), EmailMessage, BrandConfig, and the EmailSender interface. Mirrors the import-free guarantee of lib/realtime/types.ts so it compiles identically into the Node and Workers bundles. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
FrkAk
approved these changes
Jun 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Task Reference: [PYZ-266]
Adds
lib/email/types.ts— the zero-import shared contract for the email subsystem. This is the keystone file that every email transport (Cloudflare Workers, SMTP, Log, Fake) implements and every producer (templates, brand resolver, Better Auth wiring) consumes. Seven downstream tasks depend on this contract existing.Exports, in dependency order:
EmailSendResult— discriminated union{ kind: "ok"; messageId } | { kind: "error"; code; message }, following theDemoteOutcomeshape inlib/data/membership.ts. The error branch is required, forcing callers to handle both arms.EmailMessage— single-recipientto, requiredfrom/subject/html/text, optionalreplyTodistinct fromfrom, open-stringcategory(metadata only, never a routing key).BrandConfig— requiredappName/appUrl, optional brand-presentation fields (logoUrl,brandColor,footerLinks,supportEmail) so self-host renders neutral output.EmailSender— singlesend(message: EmailMessage): Promise<EmailSendResult>method.The file carries zero runtime imports so it compiles identically into the Node and Workers bundles, mirroring the import-free guarantee of
lib/realtime/types.ts.Type of change
Testing
bun run devbun run lint)bun run typecheck)Full
bun testsuite green (920 pass, 0 fail) with the docker test DB up. No new tests added — the type contract is a pure compile-time artifact, exercised by downstream PYZ-280'sFakeEmailSenderand flow tests.Notes for reviewer
Pure-types file with zero runtime surface. Three open questions deferred to downstream consumers (none have landed concrete shapes yet, so the planned shapes stand):
categorystays an openstringrather than a closed union (PYZ-269 owns the purpose→address taxonomy);footerLinkselement shape is{ label; url }[];supportEmailnaming (PYZ-269 spec says "support address"). None block this contract; downstream can tighten with confirmation.Docs impact
none
🤖 Generated with Claude Code