Send Welcome Emails from Astro with MailAnvil
A visitor signs up for your waitlist, the form submits, and then... nothing. No welcome email, no confirmation, and a lead that goes cold in minutes.
For AI-SaaS founders, the welcome email is the first impression after the signup — it either onboards the user or loses them. This tutorial wires it up in an Astro app using MailAnvil.
Why Astro + MailAnvil?
Astro ships zero JavaScript to the browser by default, but its API routes (/src/pages/api/*) still run server-side — exactly where an email send belongs. Deploy on the Cloudflare adapter and the whole stack sits on the edge: Astro pages and the MailAnvil send call run from the closest point of presence, not a us-east datacenter half a world away.
| MailAnvil | Resend | SendGrid | |
|---|---|---|---|
| 10K emails/mo | Rp 149rb | ~Rp 320rb | ~Rp 319rb |
| Payment | QRIS, GoPay | Credit card | Credit card |
| Docs | Bahasa + English | English | English |
| Latency | Edge (Jakarta PoP) | us-east | us-east |
What We Build
POST /api/welcome → Astro API route → create user → MailAnvil /v1/send → user inbox
Step 1: Scaffold Astro with the Cloudflare Adapter
npm create astro@latest foundersai -- --template minimal --install --git
cd foundersai
npx astro add cloudflare
npx astro add cloudflare wires up @astrojs/cloudflare as the SSR adapter.
Step 2: Store Secrets
Never hardcode keys. Use a .env file (or wrangler secret put on Cloudflare):
MAILANVIL_API_KEY=mk_live_xxxxxxxx
[email protected]
Step 3: The Welcome API Route
src/pages/api/welcome.ts:
import type { APIRoute } from "astro";
const BASE = "https://api.mailanvil.com/v1";
export const POST: APIRoute = async ({ request }) => {
const body = await request.json();
const email = String(body.email ?? "");
const name = String(body.name ?? "there");
if (!email || !email.includes("@")) {
return new Response(JSON.stringify({ error: "valid email required" }), {
status: 400,
headers: { "Content-Type": "application/json" },
});
}
const idempotencyKey = crypto.randomUUID();
const res = await fetch(`${BASE}/send`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${import.meta.env.MAILANVIL_API_KEY}`,
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify({
from: import.meta.env.MAILANVIL_FROM,
to: [email],
subject: `Welcome to FoundersAI, ${name}! 🎉`,
html: buildWelcomeHtml({ name }),
text: `Hi ${name}, welcome to FoundersAI. Confirm your email to join the waitlist.`,
}),
});
if (res.status !== 202) {
const err = await res.json();
return new Response(
JSON.stringify({ error: err.error?.message || "send failed" }),
{ status: 502, headers: { "Content-Type": "application/json" } },
);
}
return new Response(JSON.stringify({ queued: true }), { status: 202 });
};
import.meta.envworks because Astro exposesMAILANVIL_*(anyPUBLIC_-prefixed var is client-safe; keep secrets unprefixed so they stay server-only). On Cloudflare, add them viawrangler secret put MAILANVIL_API_KEY.
Step 4: The Welcome Template
src/lib/email.ts:
export function buildWelcomeHtml(d: { name: string }) {
return `
<div style="font-family:sans-serif;max-width:480px;margin:0 auto">
<h1 style="color:#ff801f">Welcome, ${d.name}!</h1>
<p>You're on the FoundersAI waitlist. Confirm your email to claim your spot.</p>
<a href="https://foundersai.id/confirm"
style="display:inline-block;background:#ff801f;color:#fff;padding:12px 24px;border-radius:9999px;text-decoration:none">
Confirm email
</a>
</div>`;
}
Step 5: Test
npm run dev
curl -X POST http://localhost:4321/api/welcome \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","name":"Sari"}'
Response {"queued":true} and the welcome email lands in under a second.
Real Use Cases in Indonesia
The same POST /v1/send endpoint covers the three most common transactional messages for an AI-SaaS launch:
- Waitlist welcome — confirm signup the instant it happens.
Idempotency-Key= a fresh UUID per request, so a user who double-clicks "Join" never gets two emails. - Magic-link login — AI tools skip passwords; send the link from the edge so it doesn't expire before it arrives.
- Usage/limit alerts — notify users when their API quota hits 80%. Flat IDR pricing means alert volume never surprises the bill.
Best Practices
- Idempotency-Key is mandatory — key on the signup event so a retry or double submit can't send two welcomes.
- Verify your domain first — DKIM is required to stay out of spam. MailAnvil auto-configures DKIM for domains on Cloudflare DNS.
- Watch bounces — MailAnvil's kill switch pauses sending if hard-bounce exceeds 5%, protecting your domain reputation automatically.
- Keep keys in secrets — unprefixed
import.meta.envvars stay server-only; usewrangler secret putin production.
Start Free
Free plan: 500 emails/month, no credit card. Pay with QRIS/GoPay. Full Bahasa Indonesia docs.