Next.js
Complete guide to using Unsent in Next.js applications with App Router and Pages Router
The Unsent SDK integrates seamlessly with Next.js, supporting both App Router and Pages Router. This guide covers everything from basic email sending to advanced features like campaigns, webhooks, and analytics.
Prerequisites
Node.js 16+: Next.js 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.local:
UNSENT_API_KEY=un_...Never commit your .env.local file to version control. Add it to .gitignore if not already present.
Quick Start
App Router
Create a Route Handler to send emails:
import { Unsent } from '@unsent/sdk';
import { NextRequest } from 'next/server';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function POST(request: NextRequest) {
const body = await request.json();
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) {
return Response.json({ error }, { status: 400 });
}
return Response.json(data);
}Pages Router
Create an API route:
import type { NextApiRequest, NextApiResponse } from 'next';
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
const { data, error } = await unsent.emails.send({
from: 'Acme <onboarding@unsent.dev>',
to: req.body.email,
subject: 'Welcome!',
html: '<h1>Hello!</h1><p>Thanks for signing up.</p>',
});
if (error) {
return res.status(400).json({ error });
}
return res.status(200).json(data);
}Email Operations
Send with React Email
Integrate with React Email for beautiful templates:
import { Unsent } from '@unsent/sdk';
import { WelcomeEmail } from '@/emails/welcome';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function POST(request: Request) {
const { email, name } = await request.json();
const { data, error } = await unsent.emails.send({
from: 'Team <hello@yourdomain.com>',
to: email,
subject: `Welcome ${name}!`,
react: <WelcomeEmail name={name} />,
});
if (error) {
return Response.json({ error }, { status: 400 });
}
return Response.json(data);
}Send with Attachments
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function POST(request: Request) {
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'
}]
});
return Response.json({ data, error });
}Schedule Emails
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function POST(request: Request) {
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 Response.json({ data, error });
}Batch Sending
Send up to 100 emails in one request:
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function POST(request: Request) {
const { users } = await request.json();
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 Response.json({ data, error });
}List Emails
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get('page') || '1');
const { data, count, error } = await unsent.emails.list({
page,
limit: 50
});
return Response.json({ data, count, error });
}Get Email Status
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const { data, error } = await unsent.emails.get(params.id);
return Response.json({ data, error });
}Contact Management
Create Contact Book
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function POST(request: Request) {
const { name, emoji } = await request.json();
const { data, error } = await unsent.contactBooks.create({
name,
emoji
});
return Response.json({ data, error });
}
export async function GET() {
const { data, error } = await unsent.contactBooks.list();
return Response.json({ data, error });
}Manage Contacts
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function POST(request: Request) {
const { bookId, contact } = await request.json();
const { data, error } = await unsent.contacts.create(bookId, {
email: contact.email,
firstName: contact.firstName,
lastName: contact.lastName,
subscribed: true,
metadata: contact.metadata
});
return Response.json({ data, error });
}Upsert Contact
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function POST(request: Request) {
const { bookId, contactId, contact } = await request.json();
const { data, error } = await unsent.contacts.upsert(
bookId,
contactId,
contact
);
return Response.json({ data, error });
}Campaign Management
Create Campaign
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function POST(request: Request) {
const { campaign } = await request.json();
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 Response.json({ data, error });
}
export async function GET() {
const { data, error } = await unsent.campaigns.list();
return Response.json({ data, error });
}Schedule Campaign
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function POST(
request: Request,
{ params }: { params: { id: string } }
) {
const { scheduledAt } = await request.json();
const { data, error } = await unsent.campaigns.schedule(params.id, {
scheduledAt,
batchSize: 1000,
batchInterval: 60
});
return Response.json({ data, error });
}Domain Management
List Domains
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function GET() {
const { data, error } = await unsent.domains.list();
return Response.json({ data, error });
}
export async function POST(request: Request) {
const { name, region } = await request.json();
const { data, error } = await unsent.domains.create({
name,
region
});
return Response.json({ data, error });
}Verify Domain
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function POST(
request: Request,
{ params }: { params: { id: string } }
) {
const { data, error } = await unsent.domains.verify(params.id);
return Response.json({ data, error });
}Templates
Create Template
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function POST(request: Request) {
const { template } = await request.json();
const { data, error } = await unsent.templates.create({
name: template.name,
subject: template.subject,
html: template.html,
content: template.content
});
return Response.json({ data, error });
}
export async function GET() {
const { data, error } = await unsent.templates.list();
return Response.json({ data, error });
}Send with Template
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function POST(request: Request) {
const { to, templateId, variables } = await request.json();
const { data, error } = await unsent.emails.send({
from: 'Hello <hello@yourdomain.com>',
to,
templateId,
variables
});
return Response.json({ data, error });
}Analytics
Get Account Analytics
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function GET() {
const { data, error } = await unsent.analytics.get();
return Response.json({ data, error });
}Get Time Series Data
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const days = parseInt(searchParams.get('days') || '30');
const { data, error } = await unsent.analytics.getTimeSeries({ days });
return Response.json({ data, error });
}Get Reputation Score
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function GET() {
const { data, error } = await unsent.analytics.getReputation();
return Response.json({ data, error });
}Webhooks
Manage Webhooks
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function GET() {
const { data, error } = await unsent.webhooks.list();
return Response.json({ data, error });
}
export async function POST(request: Request) {
const { url, events } = await request.json();
const { data, error } = await unsent.webhooks.create({
url,
events
});
return Response.json({ data, error });
}Receive Webhook Events
import { NextRequest } from 'next/server';
export async function POST(request: NextRequest) {
const event = await request.json();
// Handle different event types
switch (event.type) {
case 'email.delivered':
console.log('Email delivered:', event.data.emailId);
break;
case 'email.bounced':
console.log('Email bounced:', event.data.emailId);
break;
case 'email.opened':
console.log('Email opened:', event.data.emailId);
break;
case 'email.clicked':
console.log('Email clicked:', event.data.emailId);
break;
}
return Response.json({ received: true });
}Suppression Management
Manage Suppressions
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function GET() {
const { data, error } = await unsent.suppressions.list({
page: 1,
limit: 50
});
return Response.json({ data, error });
}
export async function POST(request: Request) {
const { email, reason } = await request.json();
const { data, error } = await unsent.suppressions.add({
email,
reason
});
return Response.json({ data, error });
}
export async function DELETE(request: Request) {
const { email } = await request.json();
const { data, error } = await unsent.suppressions.delete(email);
return Response.json({ data, error });
}API Key Management
Manage API Keys
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function GET() {
const { data, error } = await unsent.apiKeys.list();
return Response.json({ data, error });
}
export async function POST(request: Request) {
const { name, permission } = await request.json();
const { data, error } = await unsent.apiKeys.create({
name,
permission // 'FULL', 'SENDING', or 'READONLY'
});
return Response.json({ data, error });
}Server Actions (App Router Only)
You can also use Server Actions for form submissions:
'use server';
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function sendWelcomeEmail(formData: FormData) {
const email = formData.get('email') as string;
const { data, error } = await unsent.emails.send({
from: 'Welcome <hello@yourdomain.com>',
to: email,
subject: 'Welcome!',
html: '<h1>Thanks for joining!</h1>'
});
if (error) {
return { error: error.message };
}
return { success: true, emailId: data.emailId };
}Use in a component:
'use client';
import { sendWelcomeEmail } from '@/app/actions/email';
import { useFormState, useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? 'Sending...' : 'Subscribe'}
</button>
);
}
export function NewsletterForm() {
const [state, formAction] = useFormState(sendWelcomeEmail, null);
return (
<form action={formAction}>
<input
type="email"
name="email"
placeholder="your@email.com"
required
/>
<SubmitButton />
{state?.success && <p>Welcome email sent!</p>}
{state?.error && <p>Error: {state.error}</p>}
</form>
);
}Error Handling
Comprehensive Error Handling
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function POST(request: Request) {
try {
const { email } = await request.json();
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':
return Response.json(
{ error: 'Invalid email data' },
{ status: 400 }
);
case 'RATE_LIMIT_EXCEEDED':
return Response.json(
{ error: 'Too many requests', retryAfter: error.retryAfter },
{ status: 429 }
);
case 'DOMAIN_NOT_VERIFIED':
return Response.json(
{ error: 'Domain not verified' },
{ status: 403 }
);
default:
return Response.json(
{ error: error.message },
{ status: 500 }
);
}
}
return Response.json(data);
} catch (err) {
return Response.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}Best Practices
Singleton Pattern
Create a singleton instance to reuse across routes:
import { Unsent } from '@unsent/sdk';
let unsentInstance: Unsent | null = null;
export function getUnsent() {
if (!unsentInstance) {
if (!process.env.UNSENT_API_KEY) {
throw new Error('UNSENT_API_KEY is not defined');
}
unsentInstance = new Unsent(process.env.UNSENT_API_KEY);
}
return unsentInstance;
}Use in routes:
import { getUnsent } from '@/lib/unsent';
export async function POST(request: Request) {
const unsent = getUnsent();
// Use unsent...
}Idempotency Keys
Use idempotency keys to prevent duplicate sends:
import { Unsent } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function POST(request: Request) {
const { userId, email } = await request.json();
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 Response.json({ data, error });
}Type Safety
Leverage TypeScript for type safety:
import { Unsent } from '@unsent/sdk';
import type { SendEmailPayload } from '@unsent/sdk';
const unsent = new Unsent(process.env.UNSENT_API_KEY);
export async function POST(request: Request) {
const body = await request.json();
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 Response.json({ data, error });
}Middleware Integration
Add rate limiting or authentication:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Add custom headers or rate limiting
const response = NextResponse.next();
response.headers.set('X-Request-ID', crypto.randomUUID());
return response;
}
export const config = {
matcher: '/api/:path*',
};