← Back to all posts
2026-08-29 · MailAnvil Team

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?

Prasyarat

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:

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.