← Back to all posts
2026-08-18 · MailAnvil Team

Send Transactional Email from SvelteKit with MailAnvil (Edge + IDR Billing)

SvelteKit is the framework a lot of Indonesian indie hackers and AI SaaS builders reach for — one codebase, server routes next to components, deploy to Vercel/Netlify/Cloudflare. The piece everyone still wires up wrong is email: welcome flows, OTP codes, payment receipts.

This tutorial adds transactional email to SvelteKit in one +server.ts route with MailAnvil. No SDK dependency, no queue to run, no credit card needed to start.

Why MailAnvil

For an SvelteKit app shipping to Indonesian users, three things decide the email provider: latency to Jakarta, how you pay, and whether the docs are in your language.

MailAnvil Resend SendGrid
Delivery to Jakarta ~10ms edge (CF Workers) ~180ms ~200ms
Payment QRIS, GoPay, bank transfer Credit card only Credit card only
10k emails/mo Rp 149rb $20 (~Rp 320rb) $19.95 (~Rp 319rb)
Docs Bahasa + English English English

MailAnvil runs on Cloudflare Workers, so an API call from your SvelteKit server (or directly from a Jakarta user) is answered at the nearest edge. You pay in rupiah.

Step 1: Scaffold SvelteKit

npm create svelte@latest my-app
# choose: Skeleton project, TypeScript
cd my-app
npm install

Step 2: Add the API key

.env:

MAILANVIL_API_KEY=ma_key_xxxxxxxxxxxxx
MAILANVIL_BASE=https://api.mailanvil.com/v1

Add the key in your hosting dashboard too (Vercel/Netlify/Cloudflare) so it's available at deploy time.

Step 3: One +server.ts route

// src/routes/api/send-email/+server.ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

const BASE = import.meta.env.VITE_MAILANVIL_BASE ?? process.env.MAILANVIL_BASE!;
const KEY = process.env.MAILANVIL_API_KEY!;

export const POST: RequestHandler = async ({ request }) => {
  const { to, subject, html, text, idempotencyKey } = await request.json();

  const res = await fetch(`${BASE}/send`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${KEY}`,
      ...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}),
    },
    body: JSON.stringify({
      from: '[email protected]',
      to: [to],
      subject,
      html,
      text,
    }),
  });

  if (res.status === 202) return json({ ok: true, id: (await res.json()).id });
  const err = await res.json();
  return json({ ok: false, error: err.error?.message ?? res.status }, { status: 502 });
};

That's the whole integration. Call it from a form action, a webhook handler, or a cron job.

Step 4: Send from a form action

// src/routes/contact/+page.server.ts
import { fail } from '@sveltejs/kit';
import type { Actions } from './$types';

export const actions = {
  default: async ({ request, fetch }) => {
    const data = await request.formData();
    const to = data.get('email')?.toString() ?? '';

    const res = await fetch('/api/send-email', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        to,
        subject: 'Welcome to yourapp',
        html: '<h1>Welcome!</h1><p>Your account is ready.</p>',
        text: 'Welcome! Your account is ready.',
        idempotencyKey: crypto.randomUUID(),
      }),
    });

    return res.ok ? { success: true } : fail(502, { error: 'Send failed' });
  },
};

The idempotency pattern (don't skip)

A user who double-taps "Send verification email" should get one email, not two. MailAnvil dedupes on Idempotency-Key, so a retried request with the same key is a no-op. Generate the key once per logical action and reuse it across retries — crypto.randomUUID() per submission, or a hash of userId + action.

Real Indonesian use cases

Best practices

  1. Never hardcode from. Use a verified domain address ([email protected]) or MailAnvil rejects the send.
  2. Ship both html and text. Plain-text fallback matters for deliverability and for email clients that strip HTML.
  3. Handle 202, not 200. MailAnvil returns 202 Accepted for a queued send. Treat anything else as a failure path.
  4. Use Idempotency-Key on every retriable send.

Try it free

500 emails/month free, no credit card. Verify your domain, grab a key, and the route above works as-is.

➡️ Try MailAnvil free — mailanvil.com

Pay in IDR via QRIS or GoPay. Docs in Bahasa Indonesia and English. Support via Telegram.