# Send email with Nuxt

> Send email from a Nuxt server API route with the Emailit Node.js SDK, keep the key in private runtimeConfig and verify webhooks with h3.

This guide shows how to send email from a Nuxt 3 app. You call Emailit from a server API route, keep the API key in private `runtimeConfig`, and verify webhooks with h3 helpers.

## Prerequisites

- Nuxt 3 with a Node.js 18+ server preset.
- A [verified sending domain](/docs/domains/add-a-domain/), for example `acme.com`.
- An [API key](/docs/developers/api-keys/). A Sending Only key restricted to your domain is enough.
- Until your workspace has [production access](/docs/workspaces/production-access/), you can only send to the account emails of workspace members.

## Install the SDK

```bash
npm install @emailit/node
```

## Configure your API key

Declare private runtime config keys. Keys outside `public` are only available on the server:

```typescript title="nuxt.config.ts"
  runtimeConfig: {
    emailitApiKey: '',
    emailitWebhookSecret: '',
  },
});
```

Nuxt fills them from environment variables with the `NUXT_` prefix:

```bash title=".env"
NUXT_EMAILIT_API_KEY=secret_••••••••••••••••••••••••••••••••
NUXT_EMAILIT_WEBHOOK_SECRET=whsec_••••••••
```

> **Keep the key out of runtimeConfig.public:** Values under `runtimeConfig.public` are sent to the browser. Never put the API key there, and never call Emailit from a component or page.

Add a small helper. Files in `server/utils` are auto-imported in server routes:

```typescript title="server/utils/emailit.ts"

  return new Emailit(useRuntimeConfig(event).emailitApiKey);
}
```

If TypeScript reports a missing declaration for `@emailit/node`, add `declare module '@emailit/node';` to a `.d.ts` file in your project.

## Send an email

Create a server API route. This contact form route sends to a fixed internal address, so visitors can't use it to email arbitrary people:

```typescript title="server/api/contact.post.ts"
  const { email, message } = await readBody(event);

  if (typeof email !== 'string' || typeof message !== 'string' || !email.includes('@')) {
    throw createError({ statusCode: 400, statusMessage: 'Invalid input' });
  }

  const sent = await emailitClient(event).emails.send({
    from: 'Acme website <website@acme.com>',
    to: 'support@acme.com',
    reply_to: email,
    subject: 'New contact form message',
    text: message,
  });

  return { id: sent.id };
});
```

Call it from a page or component with `$fetch`:

```vue title="pages/contact.vue"
<script setup lang="ts">
const email = ref('');
const message = ref('');
const sent = ref(false);

async function submit() {
  await $fetch('/api/contact', {
    method: 'POST',
    body: { email: email.value, message: message.value },
  });
  sent.value = true;
}
</script>

<template>
  <form @submit.prevent="submit">
    <input v-model="email" type="email" required />
    <textarea v-model="message" required />
    <button type="submit">Send</button>
    <p v-if="sent">Thanks, we'll be in touch.</p>
  </form>
</template>
```

To send a stored template instead of inline content, pass `template` (an alias or `tem_` ID) and `variables`. See [Templates](/docs/templates/) and [Send an email](/docs/api-reference/emails/send/).

## Send with SMTP instead

If you prefer SMTP, use Nodemailer in a server route. SMTP needs a Node.js server preset; edge and worker presets can't open SMTP connections, so use the API there.

```typescript title="server/utils/mailer.ts"

  return nodemailer.createTransport({
    host: 'smtp.emailit.com',
    port: 587,
    secure: false,
    requireTLS: true,
    auth: { user: 'emailit', pass: useRuntimeConfig().emailitApiKey },
  });
}
```

Then call `await mailer().sendMail({ from, to, subject, html })` from a route. See [SMTP settings](/docs/smtp/settings/) for other ports.

## Receive webhooks

[Create a webhook](/docs/webhooks/set-up/) that points to `https://your-app.com/api/webhooks/emailit`. Verify the signature against the raw body, then parse the array of events:

```typescript title="server/api/webhooks/emailit.post.ts"

  const rawBody = (await readRawBody(event)) ?? '';
  const signature = getHeader(event, 'x-emailit-signature') ?? '';
  const timestamp = getHeader(event, 'x-emailit-timestamp') ?? '';
  const secret = useRuntimeConfig(event).emailitWebhookSecret;

  const age = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));
  const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
  const valid =
    Number.isFinite(age) &&
    age <= 300 &&
    expected.length === signature.length &&
    timingSafeEqual(Buffer.from(expected), Buffer.from(signature));

  if (!valid) {
    throw createError({ statusCode: 401, statusMessage: 'Invalid signature' });
  }

  const events = JSON.parse(rawBody) as Array<{ event_id: string; type: string; data: { object: any } }>;
  for (const item of events) {
    if (item.type === 'email.complained') {
      // Stop emailing item.data.object.to
    }
  }

  return { received: events.length };
});
```

Return a `2xx` within 30 seconds; other responses are retried. See [Request signature](/docs/webhooks/request-signature/).

## Production tips

- **Set secrets in your host.** Configure `NUXT_EMAILIT_API_KEY` in your hosting provider's environment settings rather than shipping a `.env` file.
- **Validate before you send.** Only accept a recipient from the browser when it's the signed-in user's own address, and rate-limit public routes. Every email costs credits and counts against your [sending limits](/docs/limits/).
- **Use separate keys per environment** so you can revoke a preview key without touching production.

## Next steps

  - [Node.js guide](/docs/frameworks/nodejs/): Error handling and Nodemailer details.
  - [Send email with the API](/docs/email-api/send-email/): Attachments, scheduling and tracking.
  - [Webhook event types](/docs/webhooks/event-types/): Every event and its payload.
  - [API keys](/docs/developers/api-keys/): Scopes, domain restrictions and rotation.

---
Source: https://emailit.com/docs/frameworks/nuxt/
