unsent
unsent.dev
Guides

SvelteKit

Complete guide to using Unsent in SvelteKit applications with form actions and server endpoints

The Unsent SDK integrates seamlessly with SvelteKit, leveraging form actions and server-side functions. This guide covers everything from basic email sending to advanced features like campaigns, webhooks, and analytics.

Prerequisites

Node.js 16+: SvelteKit 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_...

Add .env to your .gitignore if it's not already there to keep your API key secure.

Quick Start

Create a form action to send emails:

src/routes/+page.server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { fail } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export const actions = {
  default: async ({ request }) => {
    const formData = await request.formData();
    const email = formData.get('email') as string;

    const { data, error } = await unsent.emails.send({
      from: 'Acme <onboarding@unsent.dev>',
      to: email,
      subject: 'Welcome!',
      html: '<h1>Hello!</h1><p>Thanks for signing up.</p>',
    });

    if (error) {
      return fail(400, { error: error.message });
    }

    return { success: true, emailId: data.emailId };
  }
};

Create the form in your page:

src/routes/+page.svelte
<script>
  export let form;
</script>

<form method="POST">
  <input type="email" name="email" required placeholder="Enter your email" />
  <button type="submit">Send Email</button>
</form>

{#if form?.success}
  <p>Email sent successfully!</p>
{/if}

{#if form?.error}
  <p>Error: {form.error}</p>
{/if}

Email Operations

Send with Attachments

src/routes/api/send-invoice/+server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export async function POST({ 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'
    }]
  });

  if (error) {
    return json({ error }, { status: 400 });
  }

  return json(data);
}

Schedule Emails

src/routes/api/schedule-email/+server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export async function POST({ 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 json({ data, error });
}

Batch Sending

Send up to 100 emails in one request:

src/routes/api/send-batch/+server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export async function POST({ 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 json({ data, error });
}

List Emails

src/routes/api/emails/+server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export async function GET({ url }) {
  const page = parseInt(url.searchParams.get('page') || '1');
  
  const { data, count, error } = await unsent.emails.list({
    page,
    limit: 50
  });

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

Get Email Status

src/routes/api/emails/[id]/+server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export async function GET({ params }) {
  const { data, error } = await unsent.emails.get(params.id);
  return json({ data, error });
}

Contact Management

Create Contact Book

src/routes/api/contact-books/+server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export async function POST({ request }) {
  const { name, emoji } = await request.json();
  
  const { data, error } = await unsent.contactBooks.create({
    name,
    emoji
  });

  return json({ data, error });
}

export async function GET() {
  const { data, error } = await unsent.contactBooks.list();
  return json({ data, error });
}

Manage Contacts

src/routes/api/contacts/+server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export async function POST({ 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 json({ data, error });
}

Upsert Contact

src/routes/api/contacts/upsert/+server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export async function POST({ request }) {
  const { bookId, contactId, contact } = await request.json();
  
  const { data, error } = await unsent.contacts.upsert(
    bookId,
    contactId,
    contact
  );

  return json({ data, error });
}

Campaign Management

Create Campaign

src/routes/api/campaigns/+server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export async function POST({ 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 json({ data, error });
}

export async function GET() {
  const { data, error } = await unsent.campaigns.list();
  return json({ data, error });
}

Schedule Campaign

src/routes/api/campaigns/[id]/schedule/+server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export async function POST({ params, request }) {
  const { scheduledAt } = await request.json();
  
  const { data, error } = await unsent.campaigns.schedule(params.id, {
    scheduledAt,
    batchSize: 1000,
    batchInterval: 60
  });

  return json({ data, error });
}

Domain Management

List and Create Domains

src/routes/api/domains/+server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export async function GET() {
  const { data, error } = await unsent.domains.list();
  return json({ data, error });
}

export async function POST({ request }) {
  const { name, region } = await request.json();
  
  const { data, error } = await unsent.domains.create({
    name,
    region
  });

  return json({ data, error });
}

Verify Domain

src/routes/api/domains/[id]/verify/+server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export async function POST({ params }) {
  const { data, error } = await unsent.domains.verify(params.id);
  return json({ data, error });
}

Templates

Create Template

src/routes/api/templates/+server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export async function POST({ 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 json({ data, error });
}

export async function GET() {
  const { data, error } = await unsent.templates.list();
  return json({ data, error });
}

Send with Template

src/routes/api/send-template/+server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export async function POST({ request }) {
  const { to, templateId, variables } = await request.json();
  
  const { data, error } = await unsent.emails.send({
    from: 'Hello <hello@yourdomain.com>',
    to,
    templateId,
    variables
  });

  return json({ data, error });
}

Analytics

Get Account Analytics

src/routes/api/analytics/+server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export async function GET() {
  const { data, error } = await unsent.analytics.get();
  return json({ data, error });
}

Get Time Series Data

src/routes/api/analytics/timeseries/+server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export async function GET({ url }) {
  const days = parseInt(url.searchParams.get('days') || '30');
  
  const { data, error } = await unsent.analytics.getTimeSeries({ days });
  return json({ data, error });
}

Get Reputation Score

src/routes/api/analytics/reputation/+server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export async function GET() {
  const { data, error } = await unsent.analytics.getReputation();
  return json({ data, error });
}

Webhooks

Manage Webhooks

src/routes/api/webhooks/+server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export async function GET() {
  const { data, error } = await unsent.webhooks.list();
  return json({ data, error });
}

export async function POST({ request }) {
  const { url, events } = await request.json();
  
  const { data, error } = await unsent.webhooks.create({
    url,
    events
  });

  return json({ data, error });
}

Receive Webhook Events

src/routes/api/webhooks/unsent/+server.ts
import { json } from '@sveltejs/kit';

export async function POST({ request }) {
  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 json({ received: true });
}

Suppression Management

Manage Suppressions

src/routes/api/suppressions/+server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export async function GET() {
  const { data, error } = await unsent.suppressions.list({
    page: 1,
    limit: 50
  });
  return json({ data, error });
}

export async function POST({ request }) {
  const { email, reason } = await request.json();
  
  const { data, error } = await unsent.suppressions.add({
    email,
    reason
  });

  return json({ data, error });
}

export async function DELETE({ request }) {
  const { email } = await request.json();
  
  const { data, error } = await unsent.suppressions.delete(email);
  return json({ data, error });
}

API Key Management

Manage API Keys

src/routes/api/api-keys/+server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export async function GET() {
  const { data, error } = await unsent.apiKeys.list();
  return json({ data, error });
}

export async function POST({ request }) {
  const { name, permission } = await request.json();
  
  const { data, error } = await unsent.apiKeys.create({
    name,
    permission // 'FULL', 'SENDING', or 'READONLY'
  });

  return json({ data, error });
}

Form Actions

Enhanced Form Action

Use enhanced form actions for progressive enhancement:

src/routes/subscribe/+page.server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { fail, redirect } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export const actions = {
  subscribe: async ({ request }) => {
    const formData = await request.formData();
    const email = formData.get('email') as string;
    
    if (!email || !email.includes('@')) {
      return fail(400, { email, missing: true });
    }

    const { data, error } = await unsent.emails.send({
      from: 'Newsletter <news@yourdomain.com>',
      to: email,
      subject: 'Thanks for subscribing!',
      html: '<h1>Welcome!</h1><p>You are now subscribed to our newsletter.</p>'
    });

    if (error) {
      return fail(500, { email, error: error.message });
    }

    throw redirect(303, '/thanks');
  }
};
src/routes/subscribe/+page.svelte
<script>
  import { enhance } from '$app/forms';
  export let form;
</script>

<form method="POST" action="?/subscribe" use:enhance>
  <input
    type="email"
    name="email"
    value={form?.email ?? ''}
    required
    placeholder="your@email.com"
  />
  {#if form?.missing}
    <p class="error">Email is required</p>
  {/if}
  {#if form?.error}
    <p class="error">{form.error}</p>
  {/if}
  <button type="submit">Subscribe</button>
</form>

Error Handling

Comprehensive Error Handling

src/routes/api/send-safe/+server.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';

const unsent = new Unsent(UNSENT_API_KEY);

export async function POST({ 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 json({ error: 'Invalid email data' }, { status: 400 });
        case 'RATE_LIMIT_EXCEEDED':
          return json(
            { error: 'Too many requests', retryAfter: error.retryAfter },
            { status: 429 }
          );
        case 'DOMAIN_NOT_VERIFIED':
          return json({ error: 'Domain not verified' }, { status: 403 });
        default:
          return json({ error: error.message }, { status: 500 });
      }
    }

    return json(data);
  } catch (err) {
    return json({ error: 'Internal server error' }, { status: 500 });
  }
}

Best Practices

Singleton Pattern

Create a singleton to reuse the Unsent instance:

src/lib/server/unsent.ts
import { Unsent } from '@unsent/sdk';
import { UNSENT_API_KEY } from '$env/static/private';

let unsentInstance: Unsent | null = null;

export function getUnsent() {
  if (!unsentInstance) {
    if (!UNSENT_API_KEY) {
      throw new Error('UNSENT_API_KEY is not defined');
    }
    unsentInstance = new Unsent(UNSENT_API_KEY);
  }
  return unsentInstance;
}

Use in your endpoints:

src/routes/api/send/+server.ts
import { getUnsent } from '$lib/server/unsent';
import { json } from '@sveltejs/kit';

export async function POST({ request }) {
  const unsent = getUnsent();
  // Use unsent...
}

Idempotency Keys

Use idempotency keys to prevent duplicate sends:

src/routes/api/signup-email/+server.ts
import { getUnsent } from '$lib/server/unsent';
import { json } from '@sveltejs/kit';

export async function POST({ request }) {
  const unsent = getUnsent();
  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 json({ data, error });
}

Type Safety

Leverage TypeScript for type safety:

src/routes/api/typed-send/+server.ts
import { getUnsent } from '$lib/server/unsent';
import { json } from '@sveltejs/kit';
import type { SendEmailPayload } from '@unsent/sdk';

export async function POST({ request }) {
  const unsent = getUnsent();
  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 json({ data, error });
}

Load Functions

Use load functions to fetch data server-side:

src/routes/emails/+page.server.ts
import { getUnsent } from '$lib/server/unsent';

export async function load() {
  const unsent = getUnsent();
  
  const { data, count, error } = await unsent.emails.list({
    page: 1,
    limit: 20
  });

  return {
    emails: data,
    totalCount: count,
    error
  };
}
src/routes/emails/+page.svelte
<script>
  export let data;
</script>

<h1>Emails ({data.totalCount})</h1>

{#if data.error}
  <p>Error loading emails: {data.error.message}</p>
{:else}
  <ul>
    {#each data.emails as email}
      <li>{email.subject} - {email.status}</li>
    {/each}
  </ul>
{/if}

Resources