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

Send Transactional Emails from Deno Deploy with MailAnvil — Tutorial for AI SaaS Builders

Deno Deploy is the edge runtime of choice for AI SaaS builders. It's fast, TypeScript-native, and deploys in seconds. But every SaaS needs email — and provisioning SMTP on edge is painful.

MailAnvil solves this: REST API, no npm deps, just fetch(). Works anywhere Deno runs.

What you'll build

Three common transactional email patterns:

  1. Welcome email after signup
  2. Password reset with magic link
  3. OTP verification for 2FA

All three in under 50 lines of Deno TypeScript.

Prerequisites

Step 1: Set up your MailAnvil client

No SDK needed. Just fetch() with your API key:

// lib/mailanvil.ts
const MAILANVIL_API = "https://api.mailanvil.com/v1";
const API_KEY = Deno.env.get("MAILANVIL_API_KEY")!;

interface SendEmailParams {
  from: string;
  to: string[];
  subject: string;
  html: string;
  text?: string;
  reply_to?: string;
}

export async function sendEmail(params: SendEmailParams) {
  const res = await fetch(`${MAILANVIL_API}/emails`, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(params),
  });

  if (!res.ok) {
    const err = await res.json();
    throw new Error(`MailAnvil error: ${err.message}`);
  }

  return res.json();
}

That's it. 15 lines. No npm install, no CJS/ESM drama, no polyfills.

Step 2: Welcome email after signup

// routes/api/signup.ts
import { sendEmail } from "../../lib/mailanvil.ts";

export async function handler(req: Request): Promise<Response> {
  const { email, name } = await req.json();

  // Save user to DB...

  await sendEmail({
    from: "[email protected]",
    to: [email],
    subject: `Welcome to Yourapp, ${name}!`,
    html: `
      <h1>Welcome, ${name}!</h1>
      <p>Thanks for signing up. Here's how to get started:</p>
      <ol>
        <li>Verify your domain</li>
        <li>Send your first email</li>
        <li>Check the dashboard</li>
      </ol>
      <a href="https://yourapp.com/dashboard">Go to dashboard →</a>
    `,
  });

  return new Response(JSON.stringify({ ok: true }), {
    headers: { "Content-Type": "application/json" },
  });
}

Step 3: Password reset with magic link

// routes/api/forgot-password.ts
import { sendEmail } from "../../lib/mailanvil.ts";
import { createResetToken } from "../../lib/auth.ts";

export async function handler(req: Request): Promise<Response> {
  const { email } = await req.json();
  const token = await createResetToken(email);
  const resetUrl = `https://yourapp.com/reset-password?token=${token}`;

  await sendEmail({
    from: "[email protected]",
    to: [email],
    subject: "Reset your password",
    html: `
      <p>You requested a password reset.</p>
      <p>
        <a href="${resetUrl}">Click here to reset your password</a>
      </p>
      <p>This link expires in 1 hour.</p>
      <p>If you didn't request this, ignore this email.</p>
    `,
  });

  // Always return success (don't leak user existence)
  return new Response(JSON.stringify({ ok: true }), {
    headers: { "Content-Type": "application/json" },
  });
}

Step 4: OTP verification email

// routes/api/send-otp.ts
import { sendEmail } from "../../lib/mailanvil.ts";
import { generateOTP, storeOTP } from "../../lib/otp.ts";

export async function handler(req: Request): Promise<Response> {
  const { email } = await req.json();
  const code = generateOTP();
  await storeOTP(email, code);

  await sendEmail({
    from: "[email protected]",
    to: [email],
    subject: `Your verification code: ${code}`,
    html: `
      <div style="text-align: center; padding: 40px;">
        <h2>Your verification code</h2>
        <div style="font-size: 32px; font-weight: bold;
                    letter-spacing: 8px; padding: 20px;
                    background: #f0f0f0; border-radius: 8px;
                    margin: 20px 0;">
          ${code}
        </div>
        <p>This code expires in 5 minutes.</p>
      </div>
    `,
  });

  return new Response(JSON.stringify({ ok: true }), {
    headers: { "Content-Type": "application/json" },
  });
}

Step 5: Deploy to Deno Deploy

deployctl deploy --project=yourapp --prod

Set your MAILANVIL_API_KEY in the Deno Deploy dashboard under Settings → Environment Variables.

Why MailAnvil over alternatives on Deno Deploy

Feature MailAnvil Resend SendGrid
Deno-native (fetch) ❌ (SDK only)
No npm deps
IDR pricing
QRIS/GoPay
MCP-native
Cold start impact 0ms 0ms 200ms+

MailAnvil is the only email API where your Deno Deploy function stays fast and your billing stays in Rupiah.

Production checklist

What's next


Ready to ship? Get your API key at mailanvil.com. First 10,000 emails free.