unsent
unsent.dev
Guides

Nuxt

Complete guide to using Unsent in Nuxt applications with server routes and composables

The Unsent SDK integrates seamlessly with Nuxt, leveraging server routes and runtime configuration. This guide covers everything from basic email sending to advanced features like campaigns, webhooks, and analytics.

Prerequisites

Node.js 16+: Nuxt requires Node.js 16 or higher

Unsent API Key: Generate one in your Unsent dashboard

Verified Domain: Set up a domain in the Domains section

Installation

Install the SDK using your preferred package manager:

npm install @unsent/sdk
pnpm add @unsent/sdk
yarn add @unsent/sdk
bun add @unsent/sdk

Configuration

Add your Unsent API key to .env:

.env
UNSENT_API_KEY=un_...

Configure it in nuxt.config.ts:

nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    unsentApiKey: process.env.UNSENT_API_KEY,
  },
});

The runtimeConfig makes the API key available in server routes via useRuntimeConfig().

Quick Start

Create a server route to send emails:

server/api/send-email.post.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const body = await readBody(event);
  
  const { data, error } = await unsent.emails.send({
    from: 'Acme <onboarding@unsent.dev>',
    to: body.email,
    subject: 'Welcome!',
    html: '<h1>Hello!</h1><p>Thanks for signing up.</p>',
  });

  if (error) {
    throw createError({
      statusCode: 400,
      statusMessage: error.message,
    });
  }

  return data;
});

Call from your component:

app.vue
<script setup>
async function sendEmail() {
  await $fetch('/api/send-email', {
    method: 'POST',
    body: { email: 'user@example.com' }
  });
}
</script>

<template>
  <button @click="sendEmail">Send Email</button>
</template>

Email Operations

Send with Attachments

server/api/send-invoice.post.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { data, error } = await unsent.emails.send({
    from: 'Billing <billing@yourdomain.com>',
    to: 'customer@example.com',
    subject: 'Your Invoice',
    html: '<p>Please find your invoice attached.</p>',
    attachments: [{
      filename: 'invoice.pdf',
      content: 'base64-encoded-content',
      contentType: 'application/pdf'
    }]
  });

  if (error) {
    throw createError({ statusCode: 400, statusMessage: error.message });
  }

  return data;
});

Schedule Emails

server/api/schedule-email.post.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const scheduledTime = new Date();
  scheduledTime.setHours(scheduledTime.getHours() + 24);
  
  const { data, error } = await unsent.emails.send({
    from: 'Reminders <reminders@yourdomain.com>',
    to: 'user@example.com',
    subject: 'Meeting Reminder',
    html: '<p>Your meeting is tomorrow at 10 AM</p>',
    scheduledAt: scheduledTime.toISOString()
  });

  return { data, error };
});

Batch Sending

Send up to 100 emails in one request:

server/api/send-batch.post.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { users } = await readBody(event);
  
  const emails = users.map(user => ({
    from: 'Newsletter <news@yourdomain.com>',
    to: user.email,
    subject: 'Monthly Update',
    html: `<h1>Hi ${user.name}!</h1><p>Here's what's new...</p>`
  }));

  const { data, error } = await unsent.emails.batch(emails);
  return { data, error };
});

List Emails

server/api/emails.get.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const query = getQuery(event);
  const page = parseInt(query.page as string || '1');
  
  const { data, count, error } = await unsent.emails.list({
    page,
    limit: 50
  });

  return { data, count, error };
});

Get Email Status

server/api/emails/[id].get.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const id = getRouterParam(event, 'id');
  
  const { data, error } = await unsent.emails.get(id);
  return { data, error };
});

Contact Management

Create Contact Book

server/api/contact-books/index.post.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { name, emoji } = await readBody(event);
  
  const { data, error } = await unsent.contactBooks.create({
    name,
    emoji
  });

  return { data, error };
});

List Contact Books

server/api/contact-books/index.get.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { data, error } = await unsent.contactBooks.list();
  return { data, error };
});

Manage Contacts

server/api/contacts/create.post.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { bookId, contact } = await readBody(event);
  
  const { data, error } = await unsent.contacts.create(bookId, {
    email: contact.email,
    firstName: contact.firstName,
    lastName: contact.lastName,
    subscribed: true,
    metadata: contact.metadata
  });

  return { data, error };
});

Upsert Contact

server/api/contacts/upsert.post.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { bookId, contactId, contact } = await readBody(event);
  
  const { data, error } = await unsent.contacts.upsert(
    bookId,
    contactId,
    contact
  );

  return { data, error };
});

Campaign Management

Create Campaign

server/api/campaigns/create.post.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { campaign } = await readBody(event);
  
  const { data, error } = await unsent.campaigns.create({
    name: campaign.name,
    subject: campaign.subject,
    html: campaign.html,
    from: 'Newsletter <news@yourdomain.com>',
    contactBookId: campaign.bookId,
    replyTo: 'support@yourdomain.com'
  });

  return { data, error };
});

List Campaigns

server/api/campaigns/index.get.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { data, error } = await unsent.campaigns.list();
  return { data, error };
});

Schedule Campaign

server/api/campaigns/[id]/schedule.post.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const id = getRouterParam(event, 'id');
  const { scheduledAt } = await readBody(event);
  
  const { data, error } = await unsent.campaigns.schedule(id, {
    scheduledAt,
    batchSize: 1000,
    batchInterval: 60
  });

  return { data, error };
});

Domain Management

List and Create Domains

server/api/domains/index.get.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { data, error } = await unsent.domains.list();
  return { data, error };
});
server/api/domains/create.post.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { name, region } = await readBody(event);
  
  const { data, error } = await unsent.domains.create({
    name,
    region
  });

  return { data, error };
});

Verify Domain

server/api/domains/[id]/verify.post.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const id = getRouterParam(event, 'id');
  
  const { data, error } = await unsent.domains.verify(id);
  return { data, error };
});

Templates

Create Template

server/api/templates/create.post.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { template } = await readBody(event);
  
  const { data, error } = await unsent.templates.create({
    name: template.name,
    subject: template.subject,
    html: template.html,
    content: template.content
  });

  return { data, error };
});

List Templates

server/api/templates/index.get.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { data, error } = await unsent.templates.list();
  return { data, error };
});

Send with Template

server/api/send-template.post.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { to, templateId, variables } = await readBody(event);
  
  const { data, error } = await unsent.emails.send({
    from: 'Hello <hello@yourdomain.com>',
    to,
    templateId,
    variables
  });

  return { data, error };
});

Analytics

Get Account Analytics

server/api/analytics/index.get.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { data, error } = await unsent.analytics.get();
  return { data, error };
});

Get Time Series Data

server/api/analytics/timeseries.get.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const query = getQuery(event);
  const days = parseInt(query.days as string || '30');
  
  const { data, error } = await unsent.analytics.getTimeSeries({ days });
  return { data, error };
});

Get Reputation Score

server/api/analytics/reputation.get.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { data, error } = await unsent.analytics.getReputation();
  return { data, error };
});

Webhooks

Manage Webhooks

server/api/webhooks/index.get.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { data, error } = await unsent.webhooks.list();
  return { data, error };
});
server/api/webhooks/create.post.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { url, events } = await readBody(event);
  
  const { data, error } = await unsent.webhooks.create({
    url,
    events
  });

  return { data, error };
});

Receive Webhook Events

server/api/webhooks/unsent.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event);
  
  // Handle different event types
  switch (body.type) {
    case 'email.delivered':
      console.log('Email delivered:', body.data.emailId);
      break;
    case 'email.bounced':
      console.log('Email bounced:', body.data.emailId);
      break;
    case 'email.opened':
      console.log('Email opened:', body.data.emailId);
      break;
    case 'email.clicked':
      console.log('Email clicked:', body.data.emailId);
      break;
  }
  
  return { received: true };
});

Suppression Management

Manage Suppressions

server/api/suppressions/index.get.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { data, error } = await unsent.suppressions.list({
    page: 1,
    limit: 50
  });
  
  return { data, error };
});
server/api/suppressions/add.post.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { email, reason } = await readBody(event);
  
  const { data, error } = await unsent.suppressions.add({
    email,
    reason
  });

  return { data, error };
});
server/api/suppressions/delete.delete.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { email } = await readBody(event);
  
  const { data, error } = await unsent.suppressions.delete(email);
  return { data, error };
});

API Key Management

Manage API Keys

server/api/api-keys/index.get.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { data, error } = await unsent.apiKeys.list();
  return { data, error };
});
server/api/api-keys/create.post.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const unsent = new Unsent(config.unsentApiKey);
  
  const { name, permission } = await readBody(event);
  
  const { data, error } = await unsent.apiKeys.create({
    name,
    permission // 'FULL', 'SENDING', or 'READONLY'
  });

  return { data, error };
});

Error Handling

Comprehensive Error Handling

server/api/send-safe.post.ts
import { Unsent } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  try {
    const config = useRuntimeConfig();
    const unsent = new Unsent(config.unsentApiKey);
    
    const { email } = await readBody(event);
    
    const { data, error } = await unsent.emails.send({
      from: 'Hello <hello@yourdomain.com>',
      to: email,
      subject: 'Test',
      html: '<p>Test</p>'
    });

    if (error) {
      switch (error.code) {
        case 'VALIDATION_ERROR':
          throw createError({
            statusCode: 400,
            statusMessage: 'Invalid email data'
          });
        case 'RATE_LIMIT_EXCEEDED':
          throw createError({
            statusCode: 429,
            statusMessage: 'Too many requests',
            data: { retryAfter: error.retryAfter }
          });
        case 'DOMAIN_NOT_VERIFIED':
          throw createError({
            statusCode: 403,
            statusMessage: 'Domain not verified'
          });
        default:
          throw createError({
            statusCode: 500,
            statusMessage: error.message
          });
      }
    }

    return data;
  } catch (err) {
    throw createError({
      statusCode: 500,
      statusMessage: 'Internal server error'
    });
  }
});

Best Practices

Composable for Reusability

Create a composable to reuse the Unsent instance:

server/utils/unsent.ts
import { Unsent } from '@unsent/sdk';

let unsentInstance: Unsent | null = null;

export function useUnsent() {
  if (!unsentInstance) {
    const config = useRuntimeConfig();
    if (!config.unsentApiKey) {
      throw createError({
        statusCode: 500,
        statusMessage: 'UNSENT_API_KEY is not configured'
      });
    }
    unsentInstance = new Unsent(config.unsentApiKey);
  }
  return unsentInstance;
}

Use in server routes:

server/api/send.post.ts
export default defineEventHandler(async (event) => {
  const unsent = useUnsent();
  
  const { email } = await readBody(event);
  
  const { data, error } = await unsent.emails.send({
    from: 'Hello <hello@yourdomain.com>',
    to: email,
    subject: 'Test',
    html: '<p>Test</p>'
  });

  return { data, error };
});

Idempotency Keys

Use idempotency keys to prevent duplicate sends:

server/api/signup-email.post.ts
export default defineEventHandler(async (event) => {
  const unsent = useUnsent();
  const { userId, email } = await readBody(event);
  
  const { data, error } = await unsent.emails.send(
    {
      from: 'Welcome <hello@yourdomain.com>',
      to: email,
      subject: 'Welcome!',
      html: '<p>Thanks for signing up!</p>'
    },
    {
      idempotencyKey: `signup-${userId}`
    }
  );

  return { data, error };
});

Type Safety

Leverage TypeScript for type safety:

server/api/typed-send.post.ts
import type { SendEmailPayload } from '@unsent/sdk';

export default defineEventHandler(async (event) => {
  const unsent = useUnsent();
  const body = await readBody(event);
  
  const emailPayload: SendEmailPayload = {
    from: 'Hello <hello@yourdomain.com>',
    to: body.to,
    subject: body.subject,
    html: body.html
  };
  
  const { data, error } = await unsent.emails.send(emailPayload);
  return { data, error };
});

Client-Side Integration

Call your API from Vue components:

components/NewsletterForm.vue
<script setup>
const email = ref('');
const loading = ref(false);
const message = ref('');

async function subscribe() {
  loading.value = true;
  message.value = '';
  
  try {
    await $fetch('/api/send-email', {
      method: 'POST',
      body: { email: email.value }
    });
    message.value = 'Email sent successfully!';
    email.value = '';
  } catch (error) {
    message.value = 'Failed to send email';
  } finally {
    loading.value = false;
  }
}
</script>

<template>
  <form @submit.prevent="subscribe">
    <input
      v-model="email"
      type="email"
      placeholder="your@email.com"
      required
    />
    <button type="submit" :disabled="loading">
      {{ loading ? 'Sending...' : 'Subscribe' }}
    </button>
    <p v-if="message">{{ message }}</p>
  </form>
</template>

Resources