Kirim Email Transaksional dengan Ruby on Rails + MailAnvil
Rails masih jadi tulang punggung banyak startup dan fintech di Indonesia — marketplace, platform booking, sampai internal tool perusahaan. Umurnya panjang, ekosistem gempya, dan developer-nya banyak.
Tapi ada satu hal yang masih bikin salah langkah: kirim email transaksional. OTP, invoice, konfirmasi booking, reset password — semua email ini harus sampai cepat dan aman, bukan nyangkut di spam.
Kebanyakan developer Rails langsung pake ActionMailer + SMTP SendGrid/SES. Masalahnya: butuh credential SMTP eksternal, harga USD kena markup kurs 12-35%, dan bayar pakai kartu kredit internasional yang belum tentu kamu punya.
Tutorial ini kasih cara paling bersih kirim email dari Rails — pakai MailAnvil REST API lewat satu service class + ActiveJob, tanpa gem tambahan.
Kenapa MailAnvil?
- Harga Rupiah — gak kena markup kurs USD
- Bayar QRIS/GoPay — gak perlu kartu kredit internasional
- REST API simpel — satu endpoint
POST /v1/send - Dokumentasi Bahasa Indonesia — contoh kode dalam konteks lokal
- MCP-native — AI agent kamu bisa kirim email langsung
- Idempotency-Key — aman retry tanpa email duplikat
Prasyarat
- Ruby 3.2+
- Rails 7.x (
rails new myapp) - Akun MailAnvil (daftar gratis di mailanvil.com)
- Domain terverifikasi di MailAnvil
Step 1: Simpan API Key di Rails Credentials
Jangan hardcode API key. Rails punya credentials yang terenkripsi:
EDITOR="code --wait" bin/rails credentials:edit
Tambah di file yang kebuka:
mailanvil_api_key: re_xxxxxxxxxxxxxxxxxxxx
Akses dari kode pakai Rails.application.credentials.mailanvil_api_key.
Step 2: Service Class Reusable (Net::HTTP — stdlib)
Bikin app/services/mailanvil_client.rb. Net::HTTP udah bawaan Ruby, gak perlu gem tambahan:
# app/services/mailanvil_client.rb
require "net/http"
require "json"
class MailAnvilClient
API_BASE = "https://api.mailanvil.com/v1"
class Error < StandardError; end
def send_email(to:, subject:, from:, html: nil, text: nil, idempotency_key: nil)
raise Error, "missing mailanvil_api_key" if api_key.blank?
uri = URI("#{API_BASE}/send")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.open_timeout = 5
http.read_timeout = 15
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{api_key}"
request["Content-Type"] = "application/json"
request["Idempotency-Key"] = idempotency_key if idempotency_key
request.body = {
from: from,
to: Array(to),
subject: subject,
html: html,
text: text
}.compact.to_json
response = http.request(request)
unless response.code == "202"
body = JSON.parse(response.body) rescue {}
err = body["error"] || {}
raise Error, "MailAnvil API #{response.code}: #{err["code"]} — #{err["message"] || response.body}"
end
JSON.parse(response.body)
end
private
def api_key
Rails.application.credentials.mailanvil_api_key
end
end
Satu class, satu tanggung jawab, dipakai di seluruh app.
Step 3: Kirim via ActiveJob (Non-Blocking)
Jangan blocking HTTP response. Bungkus di ActiveJob:
# app/jobs/email_job.rb
class EmailJob < ApplicationJob
queue_as :mailers
def perform(to:, subject:, from:, html: nil, text: nil, idempotency_key: nil)
MailAnvilClient.new.send_email(
to: to, subject: subject, from: from,
html: html, text: text, idempotency_key: idempotency_key
)
end
end
Step 4: Contoh Nyata — Konfirmasi Order
Kirim email konfirmasi order saat transaksi sukses:
# app/controllers/orders_controller.rb
class OrdersController < ApplicationController
def create
order = Order.create!(order_params)
EmailJob.perform_later(
to: order.customer.email,
from: "MailAnvil <[email protected]>",
subject: "Order ##{order.id} dikonfirmasi",
html: render_to_string("orders/confirmation", locals: { order: order }, layout: "mailer"),
idempotency_key: "order-#{order.id}" # aman retry tanpa email duplikat
)
render json: { status: "ok", order_id: order.id }, status: :created
end
end
perform_later naruh job ke queue — response balik cepet, email dikirim di background. idempotency_key pastikan kalau job ke-retry, email gak terkirim dua kali.
Step 5: Template Email
app/views/orders/confirmation.html.erb:
<!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;">Order Dikonfirmasi</h1>
<p>Hai <%= order.customer.name %>, order kamu <strong>#<%= order.id %></strong> udah kami terima.</p>
<p style="font-size: 24px; font-weight: 700; color: #ff801f;">Rp <%= number_with_delimiter(order.total) %></p>
<p style="font-size: 14px; color: #666;">Simpan email ini sebagai bukti transaksi.</p>
</body>
</html>
Production Tips
1. Idempotency Key untuk Safe Retry
Kalau job ke-retry (worker crash, timeout), idempotency key bikin email yang sama gak terkirim dua kali. User gak bakal dapat dua email identik yang bikin bingung.
2. Queue Backend
perform_later butuh backend queue. Development: :async (bawaan). Production: Solid Queue (Rails 8 bawaan) atau Sidekiq.
3. Timeout & Retry
Net::HTTP di atas udah set open_timeout 5 detik dan read_timeout 15 detik. Tambah retry_on di job buat transient error:
class EmailJob < ApplicationJob
retry_on MailAnvilClient::Error, wait: :exponentially_longer, attempts: 3
# ...
end
4. Jangan Taruh Secret di Repo
Rails.application.credentials udah terenkripsi (config/credentials.yml.enc). File ini aman di-commit. Jangan pernah commit API key plaintext ke repo publik.
Perbandingan dengan Approach Lain
| Approach | Dependency | Blocking | Retry | Harga IDR |
|---|---|---|---|---|
| ActionMailer + SMTP SendGrid | credential SMTP | Tidak | Manual | USD + kurs |
| SendGrid/SES SDK | 1 gem + akun | Tidak | Built-in | USD + kurs |
| Resend SDK | 1 gem + akun | Tidak | Built-in | USD + kurs |
| MailAnvil REST | 0 gem (Net::HTTP) | Tidak (ActiveJob) | Idempotency key | IDR + QRIS |
Tanpa gem tambahan — Net::HTTP stdlib udah cukup. Satu service class, satu job, jalan.
Kesimpulan
Rails + MailAnvil = kombinasi simpel dan aman buat email transaksional. Kamu dapet:
- ✅ Zero dependency — Net::HTTP stdlib, gak nambah gem
- ✅ Non-blocking — ActiveJob background, response tetep cepet
- ✅ Idempotency key — aman retry tanpa email duplikat
- ✅ Harga Rupiah, bayar QRIS/GoPay — 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.
Butuh API key? Daftar early access di mailanvil.com — gratis 500 email/bulan.