API · Backend + Mail
The api-backend base plus transactional email wired — @voltro/plugin-mail and a React-Email welcome template you preview and send from the dashboard.
The same minimal notes base as api-backend, plus transactional email wired out of the box via @voltro/plugin-mail. It ships a React-Email welcome template and a mail.sendWelcome action. Template id: api-backend-mail.
Scaffold
voltro create-project acme --api=api-backend-mailWhat ships
apps/acme/api/ # dir named by the app, not the template
├── app.config.ts # api + mailPlugin({ provider: 'console' })
├── package.json
├── tsconfig.json
├── README.md
├── database/
│ └── schema.ts # actors + tenants + notes (same as api-backend)
├── queries/
│ ├── notes.query.ts + .query.server.ts
├── mutations/
│ └── notes.create.mutation.ts + .mutation.server.ts
├── emails/
│ └── welcome.email.tsx # a React-Email welcome template
└── actions/
└── sendWelcome.action.ts + .action.server.ts # mail.sendWelcomeThe wiring
app.config.ts adds the mail plugin with the zero-config console provider:
import { mailPlugin } from '@voltro/plugin-mail'
export default {
type: 'api' as const,
name: '{{projectNamePascal}}{{appNamePascal}}',
store: 'memory' as const,
plugins: [
mailPlugin({ provider: 'console', from: '{{projectNamePascal}} <hello@example.com>' }),
],
}The console provider logs what would be sent (no API key) and captures it in the dashboard outbox. For real delivery, switch provider to 'resend' / 'postmark' / 'sendgrid' / 'smtp' and set the matching env var (e.g. RESEND_API_KEY).
The email template — emails/welcome.email.tsx
Authored with defineEmail + @react-email/components. Auto-discovered by voltro dev and registered with the plugin, so mail.send({ template: 'welcome', props }) works and the dashboard Mail panel previews it:
import { defineEmail } from '@voltro/plugin-mail'
import { Body, Button, Container, Heading, Html, Text } from '@react-email/components'
import { Schema } from 'effect'
export const welcome = defineEmail({
name: 'welcome',
props: Schema.Struct({ name: Schema.String }),
preview: { name: 'Mario' }, // pre-fills the dashboard preview box
subject: (p) => `Welcome, ${p.name}!`,
render: (p) => (
<Html><Body><Container>
<Heading>Welcome, {p.name} 👋</Heading>
<Text>Your workspace is ready.</Text>
<Button href="https://example.com/dashboard">Open your dashboard</Button>
</Container></Body></Html>
),
})The shipped template carries prefers-color-scheme dark-mode handling and inline styles for email-client compatibility — replace the copy and brand for your product.
Sending — actions/sendWelcome.action.ts + .server.ts
Email is external I/O, so it's an action, not a mutation. The descriptor declares the wire shape; the .server.ts executor calls MailService:
// sendWelcome.action.server.ts
import { Effect } from 'effect'
import { MailService } from '@voltro/plugin-mail'
const execute = (input: { email: string; name: string }) =>
Effect.gen(function* () {
const mail = yield* MailService
const res = yield* mail.send({ to: input.email, template: 'welcome', props: { name: input.name } })
return { id: res.id, provider: res.provider }
})
export default executePreview templates and inspect the dev outbox in the dashboard's Mail tab.
Pairs well with
- Any web template — wire the
mail.sendWelcomeaction behind a signup flow.
See also
@voltro/plugin-mail— providers, templates, suppression, durable delivery.api-backend— the base this builds on.