/** * Sending mail, and the standing constraint it had to be reconciled with. * * SCOPE recorded that Taproot sends no email, on the reasoning that an external service dependency * is what would stop `npm run dev` working from a fresh clone. That reasoning is right and is kept * here — what was wrong was reading it as "no mail, ever", because self-service password reset is * *defined* by reaching someone who cannot sign in. There is no non-email version of it: an admin * handing over a link is the flow that already exists, and it is not self-service. * * So the constraint is honoured where it actually bites. With nothing configured the mailer writes * the message to the server log, which needs no account, no key, and no network — a developer sees * the reset link in the terminal and the whole flow works on a laptop. Configuring a webhook is * what turns it into real delivery. * * **No vendor is built in.** Resend, Postmark, SES and SendGrid each have their own payload shape, * auth header, and error semantics, and a CMS that ships no block templates and holds no opinion * about term URLs should not be maintaining four of them. A webhook reaches all of them through a * handful of lines on the operator's side, and adding a first-party provider later is additive. */ export interface MailMessage { to: string; subject: string; /** Always present. Some recipients strip HTML, and a reset link must survive that. */ text: string; html?: string; } export interface Mailer { /** * Reported on Settings → System, so an operator can see which one is live. * * A free string rather than a union of the two built in, because the point of the interface is * that it has more than two implementations — a test double is already a third. */ readonly name: string; /** * Whether mail actually leaves the building. * * The log mailer is a working mailer for development and a dead end in production, and the * difference decides whether the admin offers a "forgot password" link at all — a link that * silently posts into a log nobody reads is worse than no link. */ readonly delivers: boolean; send(message: MailMessage): Promise; } export declare class MailError extends Error { name: string; } export interface MailEnv { /** Where to POST the message. Its presence is what switches real delivery on. */ TAPROOT_MAIL_WEBHOOK_URL?: string; /** Sent as `authorization: Bearer …`, if set. Optional — a secret path is a valid choice too. */ TAPROOT_MAIL_WEBHOOK_TOKEN?: string; /** The `from` address, passed through to the webhook so it does not have to hardcode one. */ TAPROOT_MAIL_FROM?: string; } export declare function resolveMailer(env: MailEnv): Mailer;