Skip to main content

Sending email

Hand Nuxt Email output to a provider SDK or a local Mailpit SMTP server.

Nuxt Email renders content; your application sends it. This keeps recipients, credentials, retries, idempotency, rate limits, and delivery logs in application code where they belong.

renderEmail returns html, text, and an optional subject. Pass those values directly to your delivery client. Decide the fallback subject in the application when a template does not declare one.

Provider SDK example

Install your provider's SDK in the application. This example uses Resend's Node SDK, whose send call accepts subject, html, and text:

bash
pnpm add resend
server/utils/send-welcome.ts
import { Resend } from 'resend'

const resend = new Resend(process.env.NUXT_EMAIL_RESEND_API_KEY)

export async function sendWelcomeEmail(to: string, firstName: string) {
  const email = await renderEmail('welcome', {
    firstName,
    activationUrl: 'https://example.com/activate',
  })

  const { data, error } = await resend.emails.send({
    from: 'Example <hello@example.com>',
    to,
    subject: email.subject ?? 'Welcome',
    html: email.html,
    text: email.text,
  })

  if (error) {
    throw error
  }

  return data
}

Keep the API key server-only. Validate and authorize the action that chooses to; do not accept arbitrary recipients from an unauthenticated request. For production retries, use the provider's idempotency feature so one application event cannot send the same message twice.

Local SMTP with Mailpit

Mailpit captures development email instead of delivering it. Its defaults are SMTP on 1025 without authentication or encryption and a browser UI at http://localhost:8025. Install Nodemailer in the application:

bash
pnpm add nodemailer
pnpm add -D @types/nodemailer
server/utils/send-welcome-local.ts
import nodemailer from 'nodemailer'

const mailpit = nodemailer.createTransport({
  host: '127.0.0.1',
  port: 1025,
  secure: false,
})

export async function sendWelcomeEmailLocally(to: string, firstName: string) {
  const email = await renderEmail('welcome', {
    firstName,
    activationUrl: 'http://localhost:3000/activate',
  })

  return await mailpit.sendMail({
    from: 'Example <hello@example.test>',
    to,
    subject: email.subject ?? 'Welcome',
    html: email.html,
    text: email.text,
  })
}
  1. Start Mailpit separately.
  2. Run the application action that sends the email.
  3. Open http://localhost:8025 and inspect the captured message.

This tests the provider handoff and the full message with headers. It does not replace the real email-client checklist.

Inline images with CID attachments

Hosted HTTPS images are usually simpler. Use a Content-ID (CID) attachment when the image bytes must travel inside the message.

Reference one unique CID from the template:

app/emails/receipt.vue
<EImg src="cid:logo@example.com" alt="Example" width="120" height="32" />

Give the Nodemailer attachment the exact same cid value:

server/utils/send-receipt.ts
const email = await renderEmail('receipt', receiptProps)

await mailer.sendMail({
  from: 'Example <receipts@example.com>',
  to,
  subject: email.subject ?? 'Your receipt',
  html: email.html,
  text: email.text,
  attachments: [
    {
      filename: 'logo.png',
      path: '/application-owned-assets/logo.png',
      cid: 'logo@example.com',
    },
  ],
})

The delivery client reads the attachment bytes and creates the related Multipurpose Internet Mail Extensions (MIME) parts. Nuxt Email only preserves the cid: image source. See Nodemailer's embedded image documentation.

Ownership boundary

Nuxt Email deliberately does not provide a send function, provider adapter, queue, webhook handler, or SMTP configuration. The application owns delivery policy and can replace its provider without changing template authoring or rendering.