Kirim Invoice & Konfirmasi Pesanan Food Delivery dengan MailAnvil + Nuxt.js
Order masuk, dapur mulai masak, rider jemput — tapi pelanggan cuma lihat layar loading. Momen itu menentukan: konfirmasi pesanan yang telat satu menit saja bikin user buka aplikasi, liat nggak ada status, lalu cancel.
Untuk merchant food delivery dan food-tech startup di Indonesia, email transaksional bukan fitur pelengkap. Itu fondasi trust. Tutorial ini bangun endpoint kirim invoice + konfirmasi pesanan pakai Nuxt.js server routes + MailAnvil.
Kenapa Nuxt.js + MailAnvil?
Nuxt adalah framework paling populer untuk startup web di Indonesia (ekosistem Vue kuat di sini). Server routes-nya berjalan di Nitro, deploy ke edge atau Node — pas dengan MailAnvil yang juga edge-native.
| MailAnvil | Resend | SendGrid | |
|---|---|---|---|
| Harga 10K email | Rp 149rb/bln | ~Rp 320rb | ~Rp 319rb |
| Pembayaran | QRIS, GoPay | Kartu kredit | Kartu kredit |
| Docs | Bahasa + English | English | English |
| Latency | Edge Jakarta | us-east | us-east |
Buat merchant yang volume kirimnya tinggi (ratusan invoice per jam saat jam makan), selisih harga per-email itu langsung kerasa di margin.
Yang Kita Bangun
POST /api/send-invoice → Nuxt server route → MailAnvil /v1/send → inbox pelanggan
Step 1: Setup Nuxt
npx nuxi@latest init warung-app
cd warung-app
npm install
Step 2: Simpan API Key
Jangan hardcode. Pakai runtime config Nitro di .env:
NUXT_MAILANVIL_API_KEY=mk_live_xxxxxxxx
[email protected]
Step 3: Server Route
server/api/send-invoice.post.ts:
const BASE = 'https://api.mailanvil.com/v1';
export default defineEventHandler(async (event) => {
const body = await readBody(event);
const { email, orderId, total, items } = body;
if (!email || !orderId || !total) {
throw createError({ statusCode: 400, message: 'email, orderId, dan total wajib' });
}
const config = useRuntimeConfig();
const res = await fetch(`${BASE}/send`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${config.mailanvilApiKey}`,
'Idempotency-Key': orderId,
},
body: JSON.stringify({
from: config.mailanvilFrom,
to: [email],
subject: `Pesanan #${orderId} sedang disiapkan 🍜`,
html: buildInvoiceHtml({ orderId, total, items }),
text: `Pesanan #${orderId} senilai Rp${total} sedang disiapkan.`,
}),
});
if (res.status !== 202) {
const err = await res.json();
throw createError({ statusCode: 502, message: err.error?.message || 'Gagal kirim' });
}
return { queued: true, orderId };
});
Step 4: Template Invoice
server/utils/invoice.ts:
export function buildInvoiceHtml(d: { orderId: string; total: number; items: string[] }) {
const rows = d.items.map((i) => `<li>${i}</li>`).join('');
return `
<div style="font-family:sans-serif;max-width:480px;margin:0 auto">
<h1 style="color:#ff801f">Pesanan #${d.orderId}</h1>
<ul>${rows}</ul>
<p style="font-size:20px"><strong>Total: Rp${d.total.toLocaleString('id-ID')}</strong></p>
<p>Dapur mulai menyiapkan. Rider segera dijemput.</p>
</div>`;
}
Step 5: Test
npm run dev
curl -X POST http://localhost:3000/api/send-invoice \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","orderId":"ORD-1029","total":48500,"items":["Nasi Goreng Spesial","Es Teh Manis"]}'
Response {"queued":true,"orderId":"ORD-1029"}. Email masuk <1 detik.
Use Case Nyata di Indonesia
Pola yang sama dipakai di tiga skenario paling umum:
- Food delivery invoice — merchant GoFood/GrabFood kirim struk digital + konfirmasi pesanan. Idempotency-Key berbasis
orderIdmencegah email dobel saat server retry. - Travel booking — Tiket.com & platform travel kirim e-ticket + itinerary. Semakin cepat email keluar, semakin kecil chance user cancel.
- OTP fintech — DANA/OVO kirim kode verifikasi email sebagai fallback SMS. Latency edge = OTP nggak expired sebelum sampai.
Semua lewat satu endpoint: POST /v1/send.
Best Practices
- Idempotency-Key wajib — pakai
orderIdbiar retry nggak nge-double email. - Verifikasi domain dulu — DKIM wajib biar nggak masuk spam. MailAnvil auto-setup DKIM buat domain di Cloudflare.
- Pantau bounce — kill switch otomatis aktif kalau hard-bounce >5%. Jaga reputasi domain.
- Simpan key sebagai secret — pakai runtime config Nitro, jangan commit ke repo.
Mulai Gratis
Free plan 500 email/bulan, tanpa kartu kredit. Bayar pakai QRIS/GoPay. Docs Bahasa Indonesia lengkap.