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-mail

What 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.sendWelcome

The 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 execute

Preview templates and inspect the dev outbox in the dashboard's Mail tab.

Pairs well with

  • Any web template — wire the mail.sendWelcome action behind a signup flow.

See also