Add a mail driver
A mail driver is a small object. It sends one message through one provider. You register it on the server.
The contract
interface PlutoMailDriver {
/** The value of NUXT_PLUTO_MAIL_DRIVER that selects this driver. */
id: string
/** False when the driver cannot send yet. The default is true. */
isConfigured?: () => boolean
send: (message: PlutoMailMessage, context: PlutoMailSendContext) => Promise<PlutoMailSendResult>
}
PlutoMailMessagehasto,subject,html,text, and the optionalfrom,replyTo, andidempotencyKey.PlutoMailSendContexthas the configuredfromandreplyTo. They can be empty.PlutoMailSendResultis{ id, driver }.
Step 1: write the driver
export const myProviderDriver: PlutoMailDriver = {
id: 'my-provider',
isConfigured: () => Boolean(useRuntimeConfig().myProvider?.apiKey),
async send(message, context) {
const response = await fetch('https://api.example.com/send', {
method: 'POST',
headers: { Authorization: `Bearer ${useRuntimeConfig().myProvider.apiKey}` },
body: JSON.stringify({
from: message.from || context.from,
to: message.to,
subject: message.subject,
html: message.html,
text: message.text
})
})
if (!response.ok) {
throw new PlutoMailError('provider_error', 'The provider failed to send the message.', { status: response.status })
}
return { id: null, driver: 'my-provider' }
}
}
The example reads the key from a private runtimeConfig.myProvider.apiKey value. Declare it in the nuxt.config.ts of your layer with an empty default. Then the variable NUXT_MY_PROVIDER_API_KEY sets it at run time.
Follow these rules:
- Throw a
PlutoMailErroron failure. It takes the error code, a fixed English message, and optionalstatusandproviderCodevalues. See Mail for the codes. - Never log a recipient, a subject, a link, or a key. Never put them in an error message.
- Read a secret only on the server.
- Keep secrets in the environment.
Step 2: register the driver
Register the driver when the server starts:
export default defineNitroPlugin(() => {
registerMailDriver(myProviderDriver)
})
Step 3: select the driver
Set the driver id in the environment of the running server:
NUXT_PLUTO_MAIL_DRIVER=my-provider
If the id is not registered, Pluto uses the none driver and logs one warning. In production, Pluto never falls back to the dev driver.
Step 4: test the driver
Tests never call a real provider. Pass a fake fetch, or stub globalThis.fetch. Add a test for each error code that your driver can throw.
Optional: a mail store
A PlutoMailStore is a source of secrets other than the environment. It has an id and a get(name) function. The only name today is 'resendApiKey', so a store cannot give the secrets of your own driver. A backend layer can register one with registerMailStore. The environment always wins over a store. Read the secrets of your own driver from the environment.
Optional: an Email page section
Explain your provider setup in Settings, Email with a mailPanels entry. The guided setup of that page covers Resend only. Register the panel component with defineAsyncComponent. See Mail.