unsent
unsent.dev
Guides

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/sdk
pnpm add @unsent/sdk
yarn add @unsent/sdk
bun add @unsent/sdk

Configuration

Add your Unsent API key to .env.local:

.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:

app/api/send-email/route.ts
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:

pages/api/send-email.ts
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:

app/api/send-welcome/route.ts
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

app/api/send-invoice/route.ts
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

app/api/schedule-email/route.ts
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:

app/api/send-batch/route.ts
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

app/api/emails/route.ts
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

app/api/emails/[id]/route.ts
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

app/api/contact-books/route.ts
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

app/api/contacts/route.ts
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

app/api/contacts/upsert/route.ts
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

app/api/campaigns/route.ts
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

app/api/campaigns/[id]/schedule/route.ts
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

app/api/domains/route.ts
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

app/api/domains/[id]/verify/route.ts
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

app/api/templates/route.ts
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

app/api/send-template/route.ts
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

app/api/analytics/route.ts
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

app/api/analytics/timeseries/route.ts
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

app/api/analytics/reputation/route.ts
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

app/api/webhooks/route.ts
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

app/api/webhooks/unsent/route.ts
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

app/api/suppressions/route.ts
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

app/api/api-keys/route.ts
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:

app/actions/email.ts
'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:

app/components/newsletter-form.tsx
'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

app/api/send-safe/route.ts
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:

lib/unsent.ts
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:

app/api/send/route.ts
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:

app/api/signup-email/route.ts
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:

app/api/typed-send/route.ts
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:

middleware.ts
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*',
};

Resources