DEV Community

Drian
Drian

Posted on

TUTORIAL: Implementasi Email Aman & Anti-Blokir (Zero Bounce Policy)

Target: Developer Node.js & Laravel

Infrastruktur: Shared Hosting (Hostinger, Qwords, Niagahoster, dll)

Tujuan: Mencegah domain terblokir akibat pengiriman ke email sampah/mati.


BAGIAN 1: Persiapan Infrastruktur (Wajib)

Sebelum menyentuh kodingan, Anda wajib memastikan "SIM" (Surat Izin Mengemudi) email domain Anda valid. Tanpa ini, email Anda akan masuk Spam atau ditolak, sebagus apapun kodenya.

1. Setting DNS (SPF & DKIM)

Masuk ke hPanel (Hostinger) atau cPanel (Qwords) Anda. Cari menu "Email Deliverability" atau "DNS Zone Editor".

Pastikan record berikut ada dan aktif:

Jenis Record Nama (Host) Value (Contoh) Fungsi
TXT (SPF) @ v=spf1 include:_spf.mail.hostinger.com ~all Memberi izin server hosting mengirim email.
TXT (DKIM) default._domainkey (Kode panjang acak dari hosting) Tanda tangan digital anti-pemalsuan.

Penting: Jika status SPF/DKIM di panel hosting belum "Active" (biasanya hijau), jangan lanjut ke tahap koding. Perbaiki ini dulu dengan menghubungi support hosting jika perlu.


BAGIAN 2: Implementasi di Node.js (Express)

Kita akan membuat middleware yang memvalidasi apakah domain penerima benar-benar punya server email (MX Record) sebelum file di-upload atau email dikirim.

Langkah 1: Install Library

Kita butuh library untuk daftar domain sampah (temp mail) agar tidak perlu input manual.

npm install disposable-email-domains
Enter fullscreen mode Exit fullscreen mode

Langkah 2: Buat Helper Validasi (utils/emailValidator.js)

Copy kode ini. Kode ini menggabungkan Regex, Blacklist Manual, Anti-Disposable, dan Pengecekan DNS MX.

import dns from 'dns/promises';
import disposableDomains from 'disposable-email-domains';

/**
 * Fungsi Validasi Email Ketat
 * @param {string} email - Email yang akan dicek
 * @returns {Promise<{isValid: boolean, message: string}>}
 */
export const verifyEmailStatus = async (email) => {
  // 1. Cek Format Dasar (Regex)
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!emailRegex.test(email)) {
    return { isValid: false, message: "Format email tidak valid." };
  }

  const [localPart, domain] = email.split('@');

  // 2. Cek Blacklist User (Mencegah kirim ke bot/sistem)
  const blockedUsers = ['test', 'admin', 'root', 'noreply', 'postmaster', 'user', 'guest'];
  if (blockedUsers.includes(localPart.toLowerCase())) {
    return { isValid: false, message: "Alamat email sistem/test tidak diizinkan." };
  }

  // 3. Cek Domain Sampah (Disposable)
  if (disposableDomains.includes(domain.toLowerCase())) {
    return { isValid: false, message: "Email sementara (temp mail) ditolak." };
  }

  // 4. Cek DNS MX Record (INTI KEAMANAN)
  // Memastikan domain tersebut benar-benar melayani email
  try {
    const mxRecords = await dns.resolveMx(domain);
    if (!mxRecords || mxRecords.length === 0) {
      return { isValid: false, message: "Domain email tidak aktif/mati." };
    }
  } catch (error) {
    // Error code ENOTFOUND atau ENODATA berarti domain tidak ada
    return { isValid: false, message: "Domain email tidak ditemukan." };
  }

  return { isValid: true, message: "Email valid." };
};
Enter fullscreen mode Exit fullscreen mode

Langkah 3: Pasang di Middleware Upload

import fs from 'fs';
import { verifyEmailStatus } from './utils/emailValidator.js'; // Import helper tadi

export const uploadMiddleware = async (req, res, next) => {
  const { email } = req.body;

  // Jalankan validasi ketat
  const validation = await verifyEmailStatus(email);

  if (!validation.isValid) {
    // HAPUS FILE jika email invalid (Penting untuk hemat storage)
    if (req.file && req.file.path) {
      try {
        fs.unlinkSync(req.file.path); 
      } catch (err) {
        console.error("Gagal hapus file:", err);
      }
    }

    return res.status(400).json({ 
      status: "error", 
      message: validation.message 
    });
  }

  next();
};
Enter fullscreen mode Exit fullscreen mode

BAGIAN 3: Implementasi di Laravel (PHP)

Laravel memiliki fitur dns validation bawaan yang sangat powerfull. Anda tidak perlu koding manual untuk cek MX Record.

Langkah 1: Buat Form Request (Rekomendasi)

Jangan validasi di Controller agar kode rapi. Buat Request baru:

php artisan make:request StoreUserRequest
Enter fullscreen mode Exit fullscreen mode

Langkah 2: Terapkan Aturan Validasi

Buka file app/Http/Requests/StoreUserRequest.php:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class StoreUserRequest extends FormRequest
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'email' => [
                'required',
                'string',
                // PENTING: 'dns' akan melakukan ping ke server email penerima
                // 'rfc' memastikan format standar internet
                'email:rfc,dns', 
                // Blacklist manual
                'not_in:test@gmail.com,admin@gmail.com,user@example.com', 
            ],
            'file' => 'required|file|max:2048',
        ];
    }

    public function messages()
    {
        return [
            'email.dns' => 'Domain email tidak valid atau server email tidak ditemukan.',
            'email.email' => 'Format email salah.',
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

Langkah 3: Gunakan di Controller

public function store(StoreUserRequest $request)
{
    // Jika kode sampai sini, berarti email SUDAH PASTI VALID & AKTIF.
    // Laravel otomatis me-redirect kembali dengan error jika validasi DNS gagal.

    $path = $request->file('file')->store('uploads');

    // Lanjut logika kirim email...
}
Enter fullscreen mode Exit fullscreen mode

BAGIAN 4: Konfigurasi SMTP (Agar Tidak Terblokir)

Karena Anda menggunakan Shared Hosting, ada batasan kecepatan (Rate Limit). Jangan kirim email secepat kilat.

Setting Nodemailer (Node.js)

Gunakan opsi pool dan rateLimit agar pengiriman antre dan tidak membebani server Hostinger.

const transporter = nodemailer.createTransport({
  host: "smtp.hostinger.com", // Sesuaikan dengan hosting Anda
  port: 465,
  secure: true,
  auth: {
    user: process.env.SMTP_USER,
    pass: process.env.SMTP_PASS,
  },
  // KONFIGURASI PENTING UNTUK SHARED HOSTING:
  pool: true, // Menggunakan koneksi yang di-reuse
  maxConnections: 1, // Jangan buka banyak koneksi sekaligus
  maxMessages: 50, // Kirim maks 50 pesan per koneksi
  rateLimit: 5 // Hanya kirim 5 email per detik (Throttling)
});
Enter fullscreen mode Exit fullscreen mode

Setting Laravel

Di Laravel, cara terbaik menangani limit hosting adalah menggunakan Queue.

  1. Ubah .env: QUEUE_CONNECTION=database
  2. Jalankan php artisan queue:table dan php artisan migrate.
  3. Saat mengirim email, gunakan Mail::to($user)->queue(new WelcomeMail()); (bukan send()).
  4. Jalankan worker dengan delay: php artisan queue:work --delay=5 (Jeda 5 detik antar job).

BAGIAN 5: Checklist Keamanan & Testing

Sebelum upload ke production, lakukan tes ini.

  1. Test ke Email Sendiri:
    Kirim email dari aplikasi ke email.asli.anda@gmail.com. Apakah masuk Inbox?

  2. Cek Skor Kualitas (Mail-Tester):

  * Buka [mail-tester.com](https://www.mail-tester.com/).
  * Copy alamat email aneh yang diberikan website tersebut.
  * Kirim email dari aplikasi Anda ke alamat tersebut.
  * Cek skornya.
  * 
  * **Target:** Skor minimal **8/10**. Jika di bawah itu, cek lagi SPF/DKIM di Bagian 1.
Enter fullscreen mode Exit fullscreen mode
  1. Test Validasi Error: Coba input email ngawur@domainyanggakada123.com. Aplikasi harus menolak dan memberikan pesan error "Domain tidak valid". Jika aplikasi menerima email ini, berarti validasi MX Record Anda belum jalan.

Ringkasan "Survival Kit" Anda

  1. SPF/DKIM: Wajib aktif di hosting.
  2. Node.js: Gunakan dns.resolveMx.
  3. Laravel: Gunakan validasi email:dns.
  4. Rate Limit: Jangan kirim email massal sekaligus.
  5. Dilarang: Menggunakan test@gmail.com.

Dengan mengikuti panduan ini, Anda bisa tenang menggunakan email Hostinger/Qwords untuk production tanpa takut terblokir. Selamat coding!

Top comments (0)