A user asks for a magic link. Your handler calls the mail provider inline, the provider takes four seconds, and the request times out. Or the provider answers with a 503 and your handler has already returned 200.
Either way the user is sitting on a sign-in page waiting for an email that is not coming, and you have no record that it was ever attempted.
pithy add email makes a send a job rather than a call.
A job, not a call
When you enqueue a message, the work leaves your request. A durable Cloudflare Workflow owns the send from there — it retries, it survives a Worker restart, and it keeps a row you can look at.
That row is the difference. When a customer says they never got the reset link, you can answer whether it was attempted, whether the provider accepted it, and whether it bounced. The alternative is a support conversation conducted entirely on faith.
The kinds are the ones every product needs: magic link, one-time code, welcome, alerts. auth sends through this seam without knowing which provider you use.
The suppression list you do not wire up
Every mail system needs a suppression list — addresses that have hard-bounced, complained, or unsubscribed, which you must not mail again. Getting this wrong is not a bug, it is a deliverability problem that compounds quietly and then costs you your sending reputation.
The usual way this goes is that suppression is a thing you are supposed to remember to check.
Here it is not. The capability declares the binding, pithy add email provisions the database, and the enqueue path reads it off the environment. No consumer names it. auth sending a magic link does not know the suppression list exists, and cannot forget to consult it, because consulting it is not something auth does.
That is the pattern the whole kit uses for anything safety-adjacent: make the correct behavior the only available behavior, then make the data reachable when you want it.
Tracked, and countable
Jobs carry enough state to answer the questions you get asked. What was sent, to whom, when, in what state, and what the provider said.
There is a campaignStats seam over that for lifecycle mail, so a send to a list is countable rather than a mystery. And because the jobs are rows in your own D1, “countable” means whatever query you want to write, not whatever a vendor dashboard chose to expose.
The control-plane scopes for reading and retrying jobs are exported as constants rather than strings, and the reason is written next to them: they are the join key between what a capability requires and what pithy dashboard connect offers you to grant. A scope that differs by one character is a gate nothing ever satisfies, and the failure looks like a permissions bug rather than a typo. So nothing retypes them.
What sending actually needs from you
Two things, and neither is in your config file.
The provider credential lives in the secrets store, read through the same seam every other credential uses. This is why email composes with secrets.
The from-address and the domain are yours to own, which is the part no library can do for you. Mail that arrives is a function of SPF, DKIM and DMARC on a domain you control — Pithy sends it, but your DNS is what makes an inbox believe it.
What it does not do
It is not a marketing platform. There are no drip campaigns, no segmentation builder, no template designer. If you want those, this is the wrong layer and you should keep a real ESP for them.
It does not render your templates for you beyond the kinds it ships. The bodies are yours.
And it will not repair a domain reputation you have already damaged. The suppression list is there so you do not damage it, which only works if you have it in place before the first bounce rather than after the tenth thousand.
Why it is a capability and not a utility
You could write all of this yourself. Most teams do, twice — once badly under deadline, and once properly after the first deliverability incident.
What you get by composing it is that the durable path, the suppression check, the credential seam and the job rows arrive together and already agree with each other. Adding payments later means its receipts go out the same way. Adding support means its notifications do. None of them re-solve the problem, and none of them get to skip the suppression list.