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/sdkpnpm add @unsent/sdkyarn add @unsent/sdkbun add @unsent/sdkConfiguration
Add your Unsent API key to .env:
UNSENT_API_KEY=un_...Configure it in 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:
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:
<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
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
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:
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
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
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
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
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
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
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
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
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
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
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 };
});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
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
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
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
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
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
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
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
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 };
});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
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
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 };
});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 };
});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
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 };
});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
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:
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:
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:
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:
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:
<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>