Email reference

The surface. Using Email is the same ground with the code you write around it.

Config options

OptionDefaultMeaning
fromAddressnoreply@example.comMust use a domain onboarded onto Email Service
fromNamePithyWhat recipients see. A recognizable name improves deliverability
baseUrlPUBLIC_ORIGINWhat tracking and unsubscribe links are built against
basePath/emailWhere the management routes mount. Does not affect the recipient callbacks
themesaffronOne of four, or fine-tuned with a custom theme object
schedulerEnabledtrueWhether the every-minute scheduler runs
devDeliveryremoteUnder pithy dev: remote sends real mail; simulator logs and writes to disk

The recipient callbacks keep a fixed prefix and are unaffected by the base path, because those URLs are already minted into mail nobody can recall.

Templates

Kit copy, translated with the kit: magicLink · otp · welcome · securityAlert · invite · passwordChanged · leadCapture

Payload-carrying, only as localized as your own copy: testerNudge · supportReply · operationalNotice · newsletter · marketingCampaign

The registry is closed. A template is a kit artifact rather than something you add — which is what lets the send Worker carry every one of them in its own bundle in every language.

Job statuses

StatusInputs keptWhy
sentDroppedThe one status a retry is already refused for — retrying a delivered job is a duplicate email to a real person
failedKeptThe retry route exists for exactly this row, and re-renders from the payload
suppressed, bouncedKeptNothing was delivered. A manual block can be lifted
pending, scheduled, sendingKeptThe send has not happened yet
undispatchedKeptNot until a host Worker exists to claim it

undispatched is a truthful status, never a grave. The day the host is deployed, its first tick claims those rows.

Suppression

EventEffect
hard_bounceBlock
complaintBlock
manualBlock
unsubscribeBlocks marketing, not sign-in

One suppression database per project, not per environment: an unsubscribe in production has to stop staging too.

Management routes

Every one is control-plane and default-denied. With the seam uncomposed, all six answer 403.

MethodPathScope
GET/email/jobsemail:jobs:read
GET/email/jobs/:idemail:jobs:read
POST/email/jobs/:id/retryemail:jobs:retry
GET/email/suppressionsemail:suppressions:read
POST/email/suppressionsemail:suppressions:write
POST/email/suppressions/removeemail:suppressions:delete

Every call is audited, reads included. A block is silent to everyone it affects, so the trail is the only record it happened.

Recipient callbacks

Three, at a fixed prefix, signed, and public because the token is the credential: click, open, unsubscribe.

Every token carries its key version, so a link in a months-old email still verifies after a rotation.

Bindings

BindingTypeWritten by
DBd1pithy add
EMAIL_SUPPRESSIONSd1pithy add — the project-wide list
EMAIL_SENDERworkflowpithy add, naming the host across scripts

The Workflow entry is complete before the host exists, so local dev binds it either way.

Secrets

email-link-signing-key — minted, arbitrary, and rotatable: every token carries its version, so a rotation does not break links already in inboxes.

Provisioning

All of it is pithy email provision.

pithy email provision creates the shared suppression database, migrates it, and deploys the per-environment host.

The three routing flags are all or nothing. Enabling Email Routing on a zone points its MX at Cloudflare — so use a subdomain, never your apex.

Peer capabilities

Required: secrets.

Composed with i18n, the shell follows each job’s recorded locale.

What is not here

No segment builder, no A/B testing, no drip editor. There are campaign templates and a suppression list, and that is the extent of the marketing surface.

No inbox. Inbound mail is handled for bounces and complaints; reading and answering customer mail is support.

No deliverability guarantee. SPF, DKIM, DMARC and reputation decide that, and what you can do about each is a page of its own.

ESC