Cara Kirim Notifikasi Pembayaran QRIS + Email Invoice Otomatis dengan MailAnvil & Express.js
Setiap transaksi digital butuh konfirmasi instan. Pelanggan bayar pakai QRIS — mereka mau bukti pembayaran masuk email dalam hitungan detik. Delay 5 menit = 3x lebih tinggi chance dispute.
Di artikel ini kita bangun sistem notifikasi pembayaran yang ngirim email invoice + konfirmasi QRIS pakai MailAnvil + Express.js. Fokus ke use case Indonesia: QRIS, GoPay, dan invoice otomatis.
Kenapa MailAnvil?
Sebelum mulai kode, konteks dulu. Kenapa nggak pakai Resend atau SendGrid?
| MailAnvil | Resend | SendGrid | |
|---|---|---|---|
| Harga 10K email | Rp 149rb/bln | $20 (~Rp 320rb) | $19.95 (~Rp 319rb) |
| Pembayaran | QRIS, GoPay | Kartu kredit only | Kartu kredit only |
| Docs | Bahasa + English | English only | English only |
| Infra | CF Workers (edge) | AWS us-east-1 | AWS us-east-1 |
| Latency Jakarta | ~10ms (edge) | ~180ms | ~200ms |
Untuk startup Indonesia yang butuh invoice dan notifikasi real-time, MailAnvil kasih latency edge + pembayaran lokal. Nggak perlu pusing kartu kredit perusahaan.
Yang Kita Bangun
Flow-nya sederhana:
User bayar QRIS → Webhook payment gateway → Backend (Express.js)
→ MailAnvil /v1/send → Email invoice + konfirmasi ke user
Kita pakai Express.js karena paling banyak dipakai di ekosistem Node.js Indonesia. Pola yang sama bisa dipakai untuk payment gateway manapun: Midtrans, Xendit, Duitku, Tripay.
Step 1: Setup Project
mkdir payment-notifier && cd payment-notifier
npm init -y
npm install express dotenv
Buat file .env:
MAILANVIL_API_KEY=ma_key_xxxxxxxxxxxxx
MAILANVIL_BASE=https://api.mailanvil.com/v1
PORT=3000
Step 2: Email Sender Module
Bikin email.js — wrapper ringan untuk MailAnvil API:
// email.js
const MAILANVIL_BASE = process.env.MAILANVIL_BASE;
const MAILANVIL_API_KEY = process.env.MAILANVIL_API_KEY;
async function sendPaymentEmail({ to, customerName, amount, paymentMethod, invoiceId, items }) {
const response = await fetch(`${MAILANVIL_BASE}/send`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${MAILANVIL_API_KEY}`,
'Idempotency-Key': invoiceId, // jamin nggak kirim dobel
},
body: JSON.stringify({
from: '[email protected]',
to: [to],
subject: `Pembayaran Berhasil — Invoice #${invoiceId}`,
html: buildInvoiceHtml({ customerName, amount, paymentMethod, invoiceId, items }),
text: buildInvoiceText({ customerName, amount, paymentMethod, invoiceId, items }),
}),
});
const data = await response.json();
if (response.status === 202) {
console.log(`✅ Email terkirim: ${data.id}`);
return data;
}
throw new Error(`MailAnvil error: ${data.error?.message || response.status}`);
}
Step 3: Template Invoice HTML
Biar email keliatan profesional — bukan plain text:
function buildInvoiceHtml({ customerName, amount, paymentMethod, invoiceId, items }) {
const formatRp = (n) => new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
}).format(n);
const itemsHtml = items.map(item => `
<tr>
<td style="padding:8px;border-bottom:1px solid #eee">${item.name}</td>
<td style="padding:8px;border-bottom:1px solid #eee;text-align:center">${item.qty}x</td>
<td style="padding:8px;border-bottom:1px solid #eee;text-align:right">${formatRp(item.price)}</td>
</tr>
`).join('');
return `
<div style="max-width:600px;margin:0 auto;font-family:Arial,sans-serif;color:#222">
<div style="background:#ff801f;padding:20px;text-align:center">
<h1 style="color:#fff;margin:0;font-size:24px">Pembayaran Berhasil ✅</h1>
</div>
<div style="padding:24px">
<p>Halo <strong>${customerName}</strong>,</p>
<p>Pembayaran kamu sudah kami terima. Detail:</p>
<table style="width:100%;margin:16px 0">
<tr>
<td style="color:#666">Invoice</td>
<td style="font-weight:bold">#${invoiceId}</td>
</tr>
<tr>
<td style="color:#666">Metode</td>
<td style="font-weight:bold">${paymentMethod}</td>
</tr>
<tr>
<td style="color:#666">Total</td>
<td style="font-weight:bold;color:#ff801f;font-size:20px">${formatRp(amount)}</td>
</tr>
</table>
<table style="width:100%;border-collapse:collapse;margin:16px 0">
<thead>
<tr style="background:#f5f5f5">
<th style="padding:8px;text-align:left">Item</th>
<th style="padding:8px;text-align:center">Qty</th>
<th style="padding:8px;text-align:right">Harga</th>
</tr>
</thead>
<tbody>${itemsHtml}</tbody>
</table>
<p style="color:#666;font-size:13px;margin-top:24px">
Invoice ini diterbitkan oleh TokoKamu.id — sistem pembayaran otomatis.
Pembayaran diproses melalui ${paymentMethod}.
</p>
</div>
</div>
`;
}
Step 4: Express.js Route + Payment Webhook
// server.js
require('dotenv').config();
const express = require('express');
const { sendPaymentEmail } = require('./email');
const app = express();
app.use(express.json());
app.post('/payment-webhook', async (req, res) => {
try {
const { order_id, customer_email, customer_name, gross_amount, payment_type, items } = req.body;
// Validasi sederhana
if (!order_id || !customer_email) {
return res.status(400).json({ error: 'order_id dan customer_email wajib' });
}
// Kirim email via MailAnvil
const result = await sendPaymentEmail({
to: customer_email,
customerName: customer_name || 'Pelanggan',
amount: parseInt(gross_amount),
paymentMethod: payment_type === 'qris' ? 'QRIS' : payment_type || 'Transfer',
invoiceId: order_id,
items: items || [],
});
res.json({ success: true, email_id: result.id });
} catch (err) {
console.error('Gagal kirim notifikasi:', err.message);
res.status(500).json({ error: 'Gagal mengirim email notifikasi' });
}
});
app.listen(process.env.PORT, () => {
console.log(`Payment notifier running on port ${process.env.PORT}`);
});
Step 5: Testing Lokal
Jalankan server:
node server.js
Test dengan curl — simulasi webhook Midtrans/Xendit:
curl -X POST http://localhost:3000/payment-webhook \
-H "Content-Type: application/json" \
-d '{
"order_id": "INV-2026-0813-001",
"customer_email": "[email protected]",
"customer_name": "Budi Santoso",
"gross_amount": "149000",
"payment_type": "qris",
"items": [
{"name": "MailAnvil Starter Plan", "qty": 1, "price": 149000}
]
}'
Response 200:
{"success":true,"email_id":"em_01abc123..."}
Dan email invoice langsung masuk inbox pelanggan.
Kenapa Pola Ini Penting untuk Bisnis Indonesia
1. QRIS = Metode Pembayaran #1 di Indonesia
BI nyatet QRIS tumbuh 200% YoY. 43 juta merchant terdaftar. Nggak ngirim email konfirmasi QRIS = ninggalin 43 juta transaksi tanpa jejak.
2. Regulasi: Bukti Transaksi Elektronik
Peraturan BI dan OJK makin ketat soal bukti transaksi digital. Email invoice = audit trail yang sah dan nggak bisa disangkal.
3. Recovery Rate Dispute
GoPay dan e-commerce Indo (Tokopedia, Shopee, Blibli, Bukalapak) — semua kirim email instan setelah pembayaran. Studi internal: email konfirmasi instan nurunin dispute rate 40-60%.
4. Trust Signal untuk Pelanggan Pertama
User baru pertama kali checkout di platform kamu. Email invoice profesional = trust. Nggak ada email = mereka mikir kena scam.
Integrasi dengan Payment Gateway Populer
Midtrans
app.post('/midtrans-webhook', async (req, res) => {
const { order_id, transaction_status, fraud_status } = req.body;
if (transaction_status === 'settlement' && fraud_status === 'accept') {
// Kirim email notifikasi pembayaran sukses
}
});
Xendit Invoice
app.post('/xendit-webhook', async (req, res) => {
const { external_id, status, payer_email, amount } = req.body;
if (status === 'PAID') {
// Kirim email invoice lunas
}
});
Best Practices
- Idempotensi wajib. Webhook payment gateway bisa retry 3-5x. Tanpa
Idempotency-Key, user bisa dapat email dobel. MailAnvil handle ini server-side. - Logging semua email. MailAnvil nyimpen log 90 hari — kalau user komplain "nggak terima email", bisa dicek via dashboard.
- Domain verified. Pastikan domain pengirim (
[email protected]) sudah verified di DNS. MailAnvil auto-check DKIM/SPF saat add domain. - Rate limiting. Free plan 500 email/bulan. Starter 10K. Scale sesuai transaksi.
- Monitor bounce rate. Bounce >5% = domain direpotasi rendah. MailAnvil kasih kill switch otomatis untuk proteksi.
Mulai Gratis
Nggak perlu kartu kredit. Daftar pakai email, verify domain, langsung bisa kirim 500 email/bulan gratis.
➡️ Coba MailAnvil Gratis — mailanvil.com
QRIS? GoPay? Bisa. Docs Bahasa Indonesia? Ada. Support? Via Telegram + email.