← Back to all posts
2026-09-15 · MailAnvil Team

Kirim Email Transaksional dari NestJS dengan Mailanvil API

NestJS adalah framework Node.js yang populer untuk aplikasi enterprise — terutama fintech, SaaS, dan platform yang butuh struktur kuat. Tapi untuk email transaksional, banyak dev masih pakai SMTP manual atau provider USD mahal.

Tutorial ini cover: setup NestJS + Mailanvil API kirim email OTP, invoice, dan welcome email.

Kenapa Mailanvil untuk NestJS?

Fitur Mailanvil SendGrid Mailgun
Harga 10K/bulan Rp 149rb $20 (~Rp 320rb) $35 (~Rp 560rb)
Bayar pakai QRIS, GoPay Kartu kredit Kartu kredit
Latency dari Jakarta ~10ms (CF edge) ~180ms (US relay) ~200ms (US relay)
MCP server Native Community Community
Docs Bahasa Ya Tidak Tidak

Setup Project

# Buat project baru
npx @nestjs/cli new mailanvil-email-app
cd mailanvil-email-app

# Install dependency
npm install axios

Buat file src/mailanvil/mailanvil.service.ts:

import { Injectable } from '@nestjs/common';
import axios from 'axios';

@Injectable()
export class MailanvilService {
  private readonly apiUrl = 'https://api.mailanvil.com/v1/send';
  private readonly apiKey = process.env.MAILANVIL_API_KEY;

  async sendEmail(to: string, subject: string, html: string) {
    const response = await axios.post(
      this.apiUrl,
      {
        from: '[email protected]',
        to,
        subject,
        html,
      },
      {
        headers: {
          'Authorization': `Bearer ${this.apiKey}`,
          'Content-Type': 'application/json',
        },
      }
    );
    return response.data;
  }
}

Contoh: OTP Email

// src/auth/otp.service.ts
import { Injectable } from '@nestjs/common';
import { MailanvilService } from '../mailanvil/mailanvil.service';

@Injectable()
export class OtpService {
  constructor(private readonly mailanvil: MailanvilService) {}

  async sendOtp(email: string, code: string) {
    const html = `
      <div style="font-family: Arial, sans-serif; max-width: 400px; margin: 0 auto;">
        <h2>Kode Verifikasi Anda</h2>
        <p>Kode OTP Anda: <strong style="font-size: 24px; color: #ff801f;">${code}</strong></p>
        <p>Kode ini berlaku selama 5 menit. Jangan bagikan kode ini kepada siapa pun.</p>
      </div>
    `;

    return this.mailanvil.sendEmail(email, 'Kode Verifikasi', html);
  }
}

Contoh: Invoice Email

// src/billing/invoice.service.ts
import { Injectable } from '@nestjs/common';
import { MailanvilService } from '../mailanvil/mailanvil.service';

@Injectable()
export class InvoiceService {
  constructor(private readonly mailanvil: MailanvilService) {}

  async sendInvoice(email: string, invoice: {
    id: string;
    amount: number;
    dueDate: string;
  }) {
    const html = `
      <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
        <h2>Invoice #${invoice.id}</h2>
        <table style="width: 100%; border-collapse: collapse;">
          <tr>
            <td style="padding: 8px; border-bottom: 1px solid #eee;">Total</td>
            <td style="padding: 8px; border-bottom: 1px solid #eee; text-align: right;">
              Rp ${invoice.amount.toLocaleString('id-ID')}
            </td>
          </tr>
          <tr>
            <td style="padding: 8px; border-bottom: 1px solid #eee;">Jatuh Tempo</td>
            <td style="padding: 8px; border-bottom: 1px solid #eee; text-align: right;">
              ${invoice.dueDate}
            </td>
          </tr>
        </table>
        <p style="margin-top: 20px;">
          <a href="https://yourapp.com/invoice/${invoice.id}"
             style="background: #ff801f; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px;">
            Bayar Sekarang
          </a>
        </p>
      </div>
    `;

    return this.mailanvil.sendEmail(email, `Invoice #${invoice.id}`, html);
  }
}

Contoh: Welcome Email

// src/user/welcome.service.ts
import { Injectable } from '@nestjs/common';
import { MailanvilService } from '../mailanvil/mailanvil.service';

@Injectable()
export class WelcomeService {
  constructor(private readonly mailanvil: MailanvilService) {}

  async sendWelcome(email: string, name: string) {
    const html = `
      <div style="font-family: Arial, sans-serif; max-width: 500px; margin: 0 auto;">
        <h2>Selamat Datang, ${name}! 🎉</h2>
        <p>Terima kasih telah bergabung dengan platform kami.</p>
        <p>Berikut yang bisa kamu lakukan selanjutnya:</p>
        <ul>
          <li>Lengkapi profil kamu</li>
          <li>Eksplorasi fitur yang tersedia</li>
          <li>Hubungi support jika butuh bantuan</li>
        </ul>
        <p>Salam,<br>Tim Kami</p>
      </div>
    `;

    return this.mailanvil.sendEmail(email, 'Selamat Datang!', html);
  }
}

Error Handling

// Tambahkan di MailanvilService
async sendEmail(to: string, subject: string, html: string) {
  try {
    const response = await axios.post(
      this.apiUrl,
      { from: '[email protected]', to, subject, html },
      {
        headers: {
          'Authorization': `Bearer ${this.apiKey}`,
          'Content-Type': 'application/json',
        },
      }
    );
    return response.data;
  } catch (error) {
    if (error.response?.status === 429) {
      // Rate limited — tunggu dan retry
      await new Promise(r => setTimeout(r, 1000));
      return this.sendEmail(to, subject, html);
    }
    throw error;
  }
}

Environment Variables

# .env
MAILANVIL_API_KEY=your_api_key_here

Testing

# Jalankan app
npm run start:dev

# Test kirim email (dari terminal)
curl -X POST http://localhost:3000/auth/send-otp \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]"}'

Kenapa NestJS + MailAnvil?

  1. NestJS — struktur enterprise, dependency injection, TypeScript native
  2. Mailanvil — IDR pricing, QRIS/GoPay, CF edge latency ~10ms dari Jakarta
  3. MCP integration — AI coding agent bisa kirim email langsung dari IDE
  4. Bahasa Indonesia — docs dan support dalam Bahasa

Selanjutnya


MailAnvil API v1. Docs: docs.mailanvil.com. Pricing: mailanvil.com/pricing.