Kirim Email Transaksional dengan MailAnvil + Go — Panduan Lengkap
Go semakin populer di kalangan backend developer Indonesia — terutama di fintech, e-commerce, dan startup SaaS. Performa tinggi, binary kecil, dan concurrency model-nya bikin Go ideal untuk layanan yang butuh throughput tinggi.
Tapi ada satu hal yang bikin pusing: ngirim email dari Go app.
Kebanyakan library email Go terlalu low-level (raw SMTP) atau terlalu berat (dependency berat). Tutorial ini kasih kamu cara paling simpel kirim email transaksional dari Go — pakai MailAnvil API, tanpa dependency eksternal, cuma net/http + encoding/json.
Kenapa MailAnvil?
Sebelum masuk kode, beberapa alasan kenapa MailAnvil cocok buat Go developer Indonesia:
- Harga Rupiah — gak kena markup kurs USD 12-35%
- Bayar QRIS/GoPay — gak perlu kartu kredit internasional
- API simpel — REST standar, satu endpoint
POST /v1/send - Dokumentasi Bahasa Indonesia — contoh kode dalam konteks lokal
- MCP-native — AI agent kamu bisa kirim email langsung
Prasyarat
- Go 1.21+
- Akun MailAnvil (daftar gratis di mailanvil.com)
- Domain terverifikasi di MailAnvil
Struktur Project
go-email-app/
├── main.go
├── email/
│ └── client.go
└── go.mod
Step 1: Email Client yang Reusable
Bikin file email/client.go:
package email
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
)
const (
APIBase = "https://api.mailanvil.com/v1"
APITimeout = 15 * time.Second
)
// Client wraps MailAnvil send endpoint
type Client struct {
APIKey string
HTTPClient *http.Client
}
// NewClient creates a MailAnvil client
func NewClient(apiKey string) *Client {
return &Client{
APIKey: apiKey,
HTTPClient: &http.Client{
Timeout: APITimeout,
},
}
}
// SendRequest is the POST body for /v1/send
type SendRequest struct {
From string `json:"from"`
To []string `json:"to"`
Subject string `json:"subject"`
HTML string `json:"html,omitempty"`
Text string `json:"text,omitempty"`
ReplyTo string `json:"reply_to,omitempty"`
}
// SendResponse is returned on success (202)
type SendResponse struct {
ID string `json:"id"`
Message string `json:"message"`
}
// ErrorResponse for non-2xx responses
type ErrorResponse struct {
Error struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
// Send queues a transactional email
func (c *Client) Send(req *SendRequest) (*SendResponse, error) {
body, err := json.Marshal(req)
if err != nil {
return nil, fmt.Errorf("marshal request: %w", err)
}
httpReq, err := http.NewRequest(
http.MethodPost,
APIBase+"/send",
bytes.NewReader(body),
)
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
httpReq.Header.Set("Authorization", "Bearer "+c.APIKey)
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("Idempotency-Key", generateIdempotencyKey())
resp, err := c.HTTPClient.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("send request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusAccepted {
var result SendResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("decode response: %w", err)
}
return &result, nil
}
var errResp ErrorResponse
json.NewDecoder(resp.Body).Decode(&errResp)
return nil, fmt.Errorf("API error %d: %s — %s",
resp.StatusCode,
errResp.Error.Code,
errResp.Error.Message,
)
}
// generateIdempotencyKey creates a unique key for safe retries
func generateIdempotencyKey() string {
return fmt.Sprintf("go-%d", time.Now().UnixNano())
}
Step 2: Template Email Sederhana
Tambahin fungsi helper untuk template email yang sering dipakai:
package email
import "fmt"
// WelcomeEmail builds a welcome email
func WelcomeEmail(recipientEmail, recipientName, appName string) *SendRequest {
return &SendRequest{
From: fmt.Sprintf("%s <[email protected]>", appName),
To: []string{recipientEmail},
Subject: fmt.Sprintf("Selamat datang di %s!", appName),
HTML: fmt.Sprintf(`<!DOCTYPE html>
<html>
<body style="font-family: system-ui, sans-serif; max-width: 600px; margin: 0 auto; padding: 40px 20px; background: #000; color: #fff;">
<div style="text-align: center; padding: 40px 0;">
<h1 style="color: #ff801f;">Selamat datang, %s! 🎉</h1>
<p style="font-size: 16px; color: #aaa;">Akun kamu di %s sudah aktif.</p>
<p style="font-size: 14px; color: #666;">Mulai kirim email transaksional sekarang.</p>
<a href="https://app.mailanvil.com" style="display: inline-block; padding: 12px 32px; background: #ff801f; color: #000; text-decoration: none; border-radius: 9999px; font-weight: 600; margin-top: 20px;">Ke Dashboard →</a>
</div>
<p style="font-size: 12px; color: #444; text-align: center; margin-top: 40px;">Dikirim oleh %s via MailAnvil</p>
</body>
</html>`, recipientName, appName, appName),
ReplyTo: fmt.Sprintf("[email protected]"),
}
}
// PasswordResetEmail builds a password reset email
func PasswordResetEmail(recipientEmail, resetLink string) *SendRequest {
return &SendRequest{
From: "Security <[email protected]>",
To: []string{recipientEmail},
Subject: "Reset Password Kamu",
HTML: fmt.Sprintf(`<!DOCTYPE html>
<html>
<body style="font-family: system-ui, sans-serif; max-width: 600px; margin: 0 auto; padding: 40px 20px; background: #000; color: #fff;">
<h1 style="color: #ff801f;">Reset Password</h1>
<p style="color: #aaa;">Klik tombol di bawah untuk reset password kamu. Link berlaku 1 jam.</p>
<a href="%s" style="display: inline-block; padding: 12px 32px; background: #ff801f; color: #000; text-decoration: none; border-radius: 9999px; font-weight: 600; margin: 20px 0;">Reset Password →</a>
<p style="font-size: 12px; color: #444;">Kalau kamu tidak minta reset password, abaikan email ini.</p>
</body>
</html>`, resetLink),
}
}
// InvoiceEmail builds a payment invoice notification
func InvoiceEmail(recipientEmail, recipientName, invoiceNo, amount, paymentLink string) *SendRequest {
return &SendRequest{
From: "Billing <[email protected]>",
To: []string{recipientEmail},
Subject: fmt.Sprintf("Invoice #%s — %s", invoiceNo, amount),
HTML: fmt.Sprintf(`<!DOCTYPE html>
<html>
<body style="font-family: system-ui, sans-serif; max-width: 600px; margin: 0 auto; padding: 40px 20px; background: #000; color: #fff;">
<h1 style="color: #ff801f;">Invoice Baru</h1>
<p style="color: #aaa;">Hai %s,</p>
<div style="background: #111; padding: 24px; border-radius: 12px; border: 1px solid rgba(214,235,253,0.19); margin: 20px 0;">
<p style="margin: 0; color: #888;">Nomor Invoice</p>
<p style="margin: 4px 0 16px; font-size: 18px; font-weight: 600;">#%s</p>
<p style="margin: 0; color: #888;">Total</p>
<p style="margin: 4px 0; font-size: 24px; font-weight: 700; color: #ff801f;">%s</p>
</div>
<a href="%s" style="display: inline-block; padding: 12px 32px; background: #ff801f; color: #000; text-decoration: none; border-radius: 9999px; font-weight: 600;">Bayar Sekarang →</a>
</body>
</html>`, recipientName, invoiceNo, amount, paymentLink),
}
}
Step 3: Main Program
main.go:
package main
import (
"log"
"os"
"go-email-app/email"
)
func main() {
apiKey := os.Getenv("MAILANVIL_API_KEY")
if apiKey == "" {
log.Fatal("MAILANVIL_API_KEY environment variable required")
}
client := email.NewClient(apiKey)
// Kirim welcome email
resp, err := client.Send(email.WelcomeEmail(
"[email protected]",
"Budi",
"WarungOnline",
))
if err != nil {
log.Fatalf("send failed: %v", err)
}
log.Printf("Email queued: %s — %s", resp.ID, resp.Message)
// Kirim invoice
resp, err = client.Send(email.InvoiceEmail(
"[email protected]",
"Budi",
"INV-2026-0891",
"Rp 149.000",
"https://warungonline.com/bayar/inv-0891",
))
if err != nil {
log.Fatalf("invoice send failed: %v", err)
}
log.Printf("Invoice queued: %s", resp.ID)
}
Jalankan:
export MAILANVIL_API_KEY="re_..."
go run main.go
Output:
2026/08/09 14:30:00 Email queued: em_01J... — Email queued for delivery
2026/08/09 14:30:00 Invoice queued: em_01J...
Kenapa Gak Pakai SMTP?
Banyak Go developer yang default ke net/smtp bawaan. Masalahnya:
- Blocking I/O —
net/smtpblocking di goroutine, gak cocok buat high-concurrency Go server - Retry logic manual — kamu harus implement sendiri exponential backoff
- DKIM/SPF/DMARC sendiri — setup DNS rumit, gampang kena spam
- No monitoring — gak ada dashboard bounce/complaint
Dengan MailAnvil REST API, kamu dapet semua itu tanpa tambahan kode.
Production Tips
1. Idempotency Key untuk Safe Retry
Contoh di atas sudah include Idempotency-Key header. Ini ngejamin email gak terkirim dua kali kalau kamu retry request yang sama — penting banget untuk skenario payment notification.
2. Goroutine untuk Fire-and-Forget
Jangan blocking HTTP handler kamu:
func handleSignup(w http.ResponseWriter, r *http.Request) {
// ... simpan user ke DB ...
// Kirim welcome email async
go func() {
if _, err := emailClient.Send(email.WelcomeEmail(
user.Email, user.Name, "MyApp",
)); err != nil {
log.Printf("welcome email failed: %v", err)
// Kirim ke retry queue atau log untuk retry manual
}
}()
w.WriteHeader(http.StatusCreated)
}
3. Graceful Shutdown
Pastiin email yang lagi dikirim gak kepotong saat server shutdown:
var wg sync.WaitGroup
func handleSignup(w http.ResponseWriter, r *http.Request) {
wg.Add(1)
go func() {
defer wg.Done()
emailClient.Send(...)
}()
w.WriteHeader(http.StatusCreated)
}
// Di main():
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigCh
log.Println("Shutting down... waiting for emails to finish")
wg.Wait()
os.Exit(0)
}()
Perbandingan dengan Library Lain
| Approach | Deps | Concurrency | Retry | Harga IDR |
|---|---|---|---|---|
net/smtp |
0 (stdlib) | Manual | Manual | Tergantung SMTP |
gomail |
2 deps | OK | Manual | Tergantung SMTP |
| Resend Go SDK | 1 dep | OK | Built-in | USD + kurs |
| MailAnvil REST | 0 (stdlib) | Native goroutine | Idempotency key | IDR + QRIS |
MailAnvil approach cuma butuh net/http + encoding/json — dua-duanya dari stdlib Go. Nol dependency eksternal. Binary kamu tetep kecil, build tetep cepet.
Kesimpulan
Go + MailAnvil = kombinasi minimalis yang powerful. Kamu dapet:
- ✅ Zero external dependencies — cuma stdlib Go
- ✅ Native goroutine — kirim ribuan email concurrently
- ✅ Idempotency key — aman retry tanpa duplicate
- ✅ Harga Rupiah, bayar QRIS — gak pusing kurs USD
- ✅ Dashboard + monitoring — bounce/complaint tracking built-in
Coba sendiri: daftar di mailanvil.com, verifikasi domain, dan jalankan contoh kode di atas dalam 5 menit.
Kode lengkap tutorial ini: github.com/mailanvil/examples/go-email-client
Butuh API key? Daftar early access di mailanvil.com — gratis 500 email/bulan.