Kirim Email Transaksional dengan Spring Boot + MailAnvil
Spring Boot masih jadi pilihan utama fintech, bank digital, dan platform enterprise di Indonesia. Gojek, Tokopedia, sampai startup pembayaran baru — sebagian besar backend Java/Spring. Alasan: mature, stabil, dan banyak developer berpengalaman.
Tapi saat harus kirim email transaksional — OTP verifikasi, invoice, konfirmasi booking, reset password — banyak tim masih pake JavaMail + SMTP SendGrid/SES. Akibatnya: dependensi tambahan, credential SMTP eksternal, harga USD kena markup kurs 12-35%, dan bayar pakai kartu kredit internasional.
Tutorial ini kasih cara paling bersih kirim email dari Spring Boot — pakai MailAnvil REST API lewat RestClient (bawaan Spring 6.1+) + @Async, tanpa library 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
- Java 17+
- Spring Boot 3.2+ (
start.spring.io, dependencyspring-boot-starter-web) - Akun MailAnvil (daftar gratis di mailanvil.com)
- Domain terverifikasi di MailAnvil
Step 1: Simpan API Key di Config
Jangan hardcode API key. Taruh di application.yml:
mailanvil:
api-key: ${MAILANVIL_API_KEY}
api-base: https://api.mailanvil.com/v1
Lalu set env var MAILANVIL_API_KEY di server (atau .env lokal). Akses dari kode pakai @Value.
Step 2: DTO + Service Class (RestClient)
Bikin record untuk request dan response, lalu satu service class:
// MailAnvilService.java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
import java.util.List;
import java.util.Map;
@Service
public class MailAnvilService {
private final RestClient restClient;
public MailAnvilService(
@Value("${mailanvil.api-key}") String apiKey,
@Value("${mailanvil.api-base}") String apiBase) {
this.restClient = RestClient.builder()
.baseUrl(apiBase)
.defaultHeader("Authorization", "Bearer " + apiKey)
.defaultHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.build();
}
public record EmailRequest(
String from,
List<String> to,
String subject,
String html,
String text
) {}
public void send(EmailRequest email, String idempotencyKey) {
restClient.post()
.uri("/send")
.header("Idempotency-Key", idempotencyKey)
.body(email)
.retrieve()
.body(Map.class); // 202 Accepted = sukses, selain itu throw
}
}
Satu class, satu tanggung jawab, dipakai di seluruh app. RestClient udah bawaan Spring 6.1 — gak perlu WebClient (WebFlux) atau RestTemplate (legacy).
Step 3: Kirim Non-Blocking via @Async
Jangan blocking HTTP response. Bungkus method pemanggil dengan @Async:
// EmailSender.java
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class EmailSender {
private final MailAnvilService mailAnvil;
public EmailSender(MailAnvilService mailAnvil) {
this.mailAnvil = mailAnvil;
}
@Async
public void sendOtp(String to, String otp) {
String html = """
<div style="font-family: system-ui; background:#000; color:#fff;
max-width:600px; margin:0 auto; padding:40px 20px;">
<h1 style="color:#ff801f;">Kode Verifikasi Kamu</h1>
<p>Gunakan kode berikut untuk verifikasi akun:</p>
<p style="font-size:32px; font-weight:700; letter-spacing:6px;">%s</p>
<p style="color:#666; font-size:14px;">Kode berlaku 5 menit.</p>
</div>
""".formatted(otp);
mailAnvil.send(
new MailAnvilService.EmailRequest(
"[email protected]",
List.of(to),
"Kode Verifikasi Akun",
html,
null
),
"otp-" + to + "-" + otp // idempotency key aman retry
);
}
}
Aktifkan async di main class:
@EnableAsync
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@Async naruh task ke thread pool — response HTTP balik cepat, email dikirim di background.
Step 4: Contoh Nyata — Booking Konfirmasi
Kirim konfirmasi booking saat transaksi sukses:
// BookingController.java
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/bookings")
public class BookingController {
private final EmailSender emailSender;
public BookingController(EmailSender emailSender) {
this.emailSender = emailSender;
}
@PostMapping
public Map<String, Object> create(@RequestBody Booking booking) {
// simpan booking ke DB...
emailSender.sendOtp(booking.email(), booking.otp()); // atau email konfirmasi
return Map.of("status", "ok", "booking_id", booking.id());
}
}
Production Tips
1. Idempotency Key untuk Safe Retry
Kalau job ke-retry (crash, timeout), idempotency key bikin email yang sama gak terkirim dua kali. User gak dapat dua email identik.
2. Async TaskExecutor
Default @Async pake SimpleAsyncTaskExecutor (bikin thread baru tiap call — boros). Ganti dengan pool:
@Bean
public Executor taskExecutor() {
return new ThreadPoolTaskExecutor() {{
setCorePoolSize(4);
setMaxPoolSize(10);
setQueueCapacity(100);
initialize();
}};
}
3. Timeout & Retry
Set timeout di RestClient biar gak hang. Tambah retry untuk transient error (5xx) via spring-retry, tapi JANGAN retry email yang udah sukses diterima (202) — idempotency key udah jagain duplikat.
4. Jangan Commit Secret
MAILANVIL_API_KEY wajib env var, bukan di application.yml yang ke-commit. Kalau pake Vault/Secrets Manager, inject lewat env saat deploy.
Perbandingan dengan Approach Lain
| Approach | Dependency | Blocking | Retry | Harga IDR |
|---|---|---|---|---|
| JavaMail + SMTP SendGrid | jakarta.mail + credential SMTP | Tidak | Manual | USD + kurs |
| SendGrid/SES SDK | 1 SDK + akun | Tidak | Built-in | USD + kurs |
| Resend SDK | 1 SDK + akun | Tidak | Built-in | USD + kurs |
| MailAnvil REST | 0 (RestClient bawaan) | Tidak (@Async) | Idempotency key | IDR + QRIS |
Tanpa library tambahan — RestClient bawaan Spring udah cukup. Satu service class, satu @Async sender, jalan.
Kesimpulan
Spring Boot + MailAnvil = kombinasi simpel dan aman buat email transaksional. Kamu dapet:
- ✅ Zero dependency —
RestClientbawaan Spring, gak nambah SDK - ✅ Non-blocking —
@Asyncbackground, response tetap cepat - ✅ Idempotency key — aman retry tanpa email duplikat
- ✅ Harga Rupiah, bayar QRIS/GoPay — gak pusing kurs USD
- ✅ Dashboard + monitoring — bounce/complaint tracking built-in
Cocok buat fintech (OTP verifikasi), platform travel (konfirmasi booking), food delivery (invoice pesanan), dan e-commerce (notifikasi order).
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.