Cara Setup Email Authentication di CI/CD Pipeline untuk Automated Email Verification Sebelum Production Deploy

Deployment yang tidak verify email authentication record bisa bikin semua email transaksional (OTP, invoice, password reset) masuk spam sebelum Anda sadari. Artikel ini membahas cara otomatiskan verifikasi SPF, DKIM, dan DMARC records di GitHub Actions dan GitLab CI, sehingga broken authentication caught sebelum code masuk production.


Email authentication record yang expired atau misconfigured adalah masalah yang tidak terlihat sampai aplikasi sudah di production. Anda deploy, user mulai complain email tidak masuk, baru tahu bahwa SPF record belum diupdate setelah migration. Satu deployment bisa merusak reputasi domain untuk berminggu-minggu.

Masalahnya, developer biasanya test email authentication secara manual: buka DNS checker, ketik domain, baca hasilnya. Ini tidak scalable kalau Anda deploy beberapa kali sehari. CI/CD pipeline yang otomatis verify email authentication records sebelum setiap deployment adalah satu-satunya cara untuk catch masalah ini secara konsisten.

Kenapa Email Authentication Harus Di-Test di CI/CD Pipeline?

Setiap kali Anda deploy aplikasi yang kirim email, ada risiko konfigurasi email authentication berubah tanpa sengaja. Beberapa skenario yang sering terjadi:

DNS record expired atau salah. DKIM selector salah ketik saat setup awal. Migrasi ke SMTP provider baru tanpa update SPF record. Perubahan infrastruktur yang inadvertently remove DMARC policy. Semua skenario ini bisa dideteksi oleh automated tests yang jalan di CI/CD pipeline sebelum deployment berhasil.

tanpa automated test, masalah baru ketahuan setelah user complain. Dengan automated test, masalah ketahuan dalam hitungan detik setelah Anda push code. Kalau tim Anda pakai Laravel untuk email queue, workflow ini juga relevant karena deployment sering bersamaan dengan konfigurasi mail driver yang salah; baca juga cara setup SMTP di Laravel untuk konteks tambahan.

Email transaksional yang masuk spam punya dampak langsung ke bisnis: OTP tidak sampai, user tidak bisa login. Invoice tidak sampai, payment terlambat. Password reset gagal, user churn. Semua ini bisa dihindari dengan satu pipeline yang verify email authentication sebelum deployment. Salah satu penyebab email transaksional masuk spam adalah konfigurasi SMTP yang belum lengkap, sesuatu yang sering terlewat padahal dampaknya besar.

Apa yang Dicek di Pipeline? SPF, DKIM, dan DMARC

Automated email authentication testing di CI/CD pipeline berfokus pada tiga record utama.

SPF (Sender Policy Framework) adalah TXT record di DNS yang mendaftarkan IP mana saja yang boleh kirim email atas nama domain Anda. Record ini berbentuk v=spf1 include:mail.provider.com ~all atau variasi lainnya. Di pipeline, Anda verify bahwa record ini ada dan mengandung include statement yang valid untuk SMTP provider yang Anda pakai. Kalau Anda belum familiar dengan cara setup SPF record, baca panduan SPF lengkap di KIRIM.EMAIL.

DKIM (DomainKeys Identified Mail) adalah record yang menyimpan public key untuk verifikasi signature digital di email header. DKIM record biasanya berbentuk CNAME yang menunjuk ke selector tertentu, misalnya ke1._domainkey.domain.com. Pipeline Anda perlu verify bahwa record ini resolve ke public key yang valid.

DMARC (Domain-based Message Authentication, Reporting and Conformance) adalah policy record yang menginstruksikan receiving server apa yang harus dilakukan kalau email gagal SPF atau DKIM check. Record DMARC berbentuk v=DMARC1; p=quarantine; rua=mailto:[email protected]. Pipeline Anda verify bahwa policy ini ada dan formatnya valid.

Semua tiga record ini harus dicek setiap kali sebelum deployment. Kalau satu saja yang missing atau malformed, pipeline harus fail dan blok deployment. Untuk memahami metric yang perlu dimonitor setelah authentication berjalan.

Setup GitHub Actions untuk Email Authentication Verification

Buat file baru di repository Anda: .github/workflows/email-auth-check.yml. Isi workflow ini dengan step yang verify DNS records sebelum application deployment dimulai.

name: Email Authentication Check

on:
  pull_request:
    branches: [main, production]
  push:
    branches: [main, production]

jobs:
  email-auth-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install DNS lookup tools
        run: |
          sudo apt-get update
          sudo apt-get install -y dnsutils curl jq

      - name: Check SPF record
        run: |
          DOMAIN="${{ vars.EMAIL_DOMAIN }}"
          SPF_RECORD=$(dig +short TXT "$DOMAIN" | grep "v=spf1" | head -1)
          if [ -z "$SPF_RECORD" ]; then
            echo "ERROR: SPF record not found for $DOMAIN"
            exit 1
          fi
          echo "SPF record: $SPF_RECORD"

      - name: Check DKIM record
        run: |
          DOMAIN="${{ vars.EMAIL_DOMAIN }}"
          SELECTOR="${{ vars.DKIM_SELECTOR }}"
          DKIM_HOST="${SELECTOR}._domainkey.${DOMAIN}"
          DKIM_RECORD=$(dig +short CNAME "$DKIM_HOST" | head -1)
          if [ -z "$DKIM_RECORD" ]; then
            echo "ERROR: DKIM record not found for $DKIM_HOST"
            exit 1
          fi
          echo "DKIM record: $DKIM_RECORD"

      - name: Check DMARC record
        run: |
          DOMAIN="${{ vars.EMAIL_DOMAIN }}"
          DMARC_HOST="_dmarc.${DOMAIN}"
          DMARC_RECORD=$(dig +short TXT "$DMARC_HOST" | grep "v=DMARC1" | head -1)
          if [ -z "$DMARC_RECORD" ]; then
            echo "WARNING: DMARC record not found for $DOMAIN"
          else
            echo "DMARC record: $DMARC_RECORD"
          fi

Workflow ini jalan di setiap push ke branch main atau production, dan juga di setiap pull request. Kalau SPF atau DKIM record missing, workflow fail dan deployment tidak bisa proceed.

Untuk membaca variable EMAIL_DOMAIN dan DKIM_SELECTOR, set di repository settings: Settings > Variables and secrets > Variables. Ini membuat workflow portable: kalau Anda clone repository ke project lain, cukup update variable-nya.

Setup GitLab CI untuk Email Authentication Verification

Jika repository Anda di GitLab, buat file .gitlab-ci.yml di root project:

stages:
  - pre-deploy
  - deploy

email-auth-check:
  stage: pre-deploy
  image: alpine:latest
  before_script:
    - apk add --no-cache bind-tools curl jq bash
  script:
    - |
      DOMAIN="$EMAIL_DOMAIN"
      SELECTOR="$DKIM_SELECTOR"
      
      echo "Checking SPF record for $DOMAIN..."
      SPF_RECORD=$(dig +short TXT "$DOMAIN" @8.8.8.8 | grep "v=spf1" | head -1)
      if [ -z "$SPF_RECORD" ]; then
        echo "ERROR: SPF record not found"
        exit 1
      fi
      echo "SPF OK: $SPF_RECORD"

      echo "Checking DKIM record for $SELECTOR..."
      DKIM_HOST="${SELECTOR}._domainkey.${DOMAIN}"
      DKIM_RECORD=$(dig +short CNAME "$DKIM_HOST" @8.8.8.8 | head -1)
      if [ -z "$DKIM_RECORD" ]; then
        echo "ERROR: DKIM record not found"
        exit 1
      fi
      echo "DKIM OK: $DKIM_RECORD"

      echo "Checking DMARC record..."
      DMARC_HOST="_dmarc.${DOMAIN}"
      DMARC_RECORD=$(dig +short TXT "$DMARC_HOST" @8.8.8.8 | grep "v=DMARC1" | head -1)
      if [ -z "$DMARC_RECORD" ]; then
        echo "WARNING: DMARC record not found"
      else
        echo "DMARC OK: $DMARC_RECORD"
      fi

  variables:
    EMAIL_DOMAIN: "yourdomain.com"
    DKIM_SELECTOR: "ke1"
  only:
    - main
    - production

GitLab CI menggunakan Alpine Linux image yang ringan. Install bind-tools untuk command dig, lalu jalankan same DNS checks. DMARC record sengaja hanya warning karena beberapa domain belum adopt DMARC dari awal.

Set variable EMAIL_DOMAIN dan DKIM_SELECTOR di GitLab: Settings > CI/CD > Variables. Pastikan variable bertipe “Masked” untuk DKIM selector kalau mengandung sensitive information.

Advanced: Verify Email Authentication dengan Python Script

Kalau tim Anda butuh lebih dari basic DNS lookup, Anda bisa tulis Python script yang juga validate format dan content dari setiap record. Ini berguna kalau Anda mau check specific include statements di SPF record atau specific flags di DMARC policy.

#!/usr/bin/env python3
import dns.resolver
import sys

def check_spf(domain):
    try:
        answers = dns.resolver.resolve(domain, 'TXT')
        for rdata in answers:
            for txt_string in rdata.strings:
                if txt_string.decode().startswith('v=spf1'):
                    spf = txt_string.decode()
                    print(f"SPF found: {spf}")
                    if 'include:' in spf:
                        includes = [p for p in spf.split() if p.startswith('include:')]
                        print(f"SPF includes: {includes}")
                    return True
        print("SPF record not found")
        return False
    except dns.resolver.NXDOMAIN:
        print(f"Domain {domain} does not exist")
        return False

def check_dkim(domain, selector):
    dkim_host = f"{selector}._domainkey.{domain}"
    try:
        answers = dns.resolver.resolve(dkim_host, 'CNAME')
        for rdata in answers:
            print(f"DKIM CNAME: {rdata.target}")
        return True
    except dns.resolver.NXDOMAIN:
        print(f"DKIM record not found for {dkim_host}")
        return False
    except dns.resolver.NoAnswer:
        # Maybe it's a TXT record instead
        try:
            answers = dns.resolver.resolve(dkim_host, 'TXT')
            for rdata in answers:
                for txt_string in rdata.strings:
                    print(f"DKIM TXT: {txt_string.decode()}")
            return True
        except:
            print(f"DKIM record not found for {dkim_host}")
            return False

def check_dmarc(domain):
    dmarc_host = f"_dmarc.{domain}"
    try:
        answers = dns.resolver.resolve(dmarc_host, 'TXT')
        for rdata in answers:
            for txt_string in rdata.strings:
                if txt_string.decode().startswith('v=DMARC1'):
                    print(f"DMARC found: {txt_string.decode()}")
                    return True
        print("DMARC record not found")
        return False
    except dns.resolver.NXDOMAIN:
        print(f"DMARC record not found for {dmarc_host}")
        return False

if __name__ == "__main__":
    domain = sys.argv[1] if len(sys.argv) > 1 else "example.com"
    selector = sys.argv[2] if len(sys.argv) > 2 else "default"

    print(f"Checking email authentication for {domain}...")
    spf_ok = check_spf(domain)
    dkim_ok = check_dkim(domain, selector)
    dmarc_ok = check_dmarc(domain)

    if not all([spf_ok, dkim_ok]):
        print("\nEmail authentication check FAILED")
        sys.exit(1)
    else:
        print("\nEmail authentication check PASSED")
        sys.exit(0)

Script ini bisa Anda integrasikan ke pre-deployment hook atau jalankan sebagai Docker container di pipeline. Kelebihan dibanding bash script: bisa handle CNAME dan TXT record untuk DKIM, bisa parse SPF include statements, dan outputnya structured untuk parsing automated.

Kapan Email Authentication Harus Dicek? Sebelum Merge atau Sebelum Deploy?

Ada dua strategi untuk integrate email authentication checks ke pipeline: sebagai gate sebelum merge (pull request) atau sebagai gate sebelum deploy (push ke production).

Strategi pull request lebih baik untuk tim yang melakukan code review. Email authentication di-test setiap kali ada PR yang mengubah konfigurasi email. Ini menangkap masalah lebih awal, sebelum code masuk branch utama. Kelemahannya: tidak menangkap kasus di mana DNS record berubah di luar pipeline (misalnya manual change di DNS provider).

Strategi push ke production lebih baik untuk menangkap semua perubahan, termasuk yang dilakukan di DNS provider langsung. Ini menangkap masalah meskipun seseorang lupa meng-notifikasi tim tentang DNS change. Namun, ini tidak mencegah broken code yang sudah di-merge ke main.

Pendekatan terbaik adalah kombinasi keduanya: test di PR untuk feedback cepat, dan test lagi sebelum deployment untuk confidence tambahan. Workflow GitHub Actions di atas sudah mendukung kedua trigger (on: [pull_request, push]).

Handling Email Authentication di Environment yang Berbeda

Aplikasi production biasanya punya domain yang berbeda dari staging atau development environment. Email authentication record biasanya hanya di-setup untuk domain production. Untuk environment lain, Anda bisa skip DKIM check tetapi tetap verify SPF.

Modifikasi workflow untuk environment-specific logic:

- name: Check SPF for all environments
  run: |
    DOMAIN="${{ vars.EMAIL_DOMAIN }}"
    # Fallback to staging domain if set
    if [ "${{ vars.STAGING_DOMAIN }}" != "" ]; then
      DOMAIN="${{ vars.STAGING_DOMAIN }}"
    fi
    # ... rest of SPF check

- name: Check DKIM only for production
  if: github.ref == 'refs/heads/production'
  run: |
    # ... DKIM check only runs for production branch

Dengan conditional execution ini, staging environment tidak blocked oleh missing DKIM record yang memang belum diperlukan di sana.

FAQ

Apa yang harus dilakukan kalau email authentication check gagal di pipeline?

Kalau pipeline gagal karena SPF atau DKIM record missing, langsung ke DNS provider Anda dan verify bahwa record tersebut ada. Kadang record sudah di-set tapi belum propagate: DNS propagation bisa memakan waktu hingga 24 jam. Kalau baru saja menambahkan record, tunggu dan jalankan pipeline lagi. Kalau record sudah ada tapi masih gagal, periksa apakah selector DKIM sudah sesuai dengan apa yang dikonfigurasi di SMTP provider Anda.

Bagaimana kalau DNS provider tidak punya API untuk automated update?

Kalau DNS provider tidak menyediakan API, Anda bisa combine automated checks dengan manual update procedure. Pipeline tidak perlu mengupdate DNS secara otomatis; cukup verify bahwa records sudah benar sebelum deployment. Untuk tim yang pakai Terraform, Anda bisa setup Terraform workflow yang apply DNS changes sebagai bagian dari deployment pipeline, dengan approval gate untuk production changes.

Apakah saya perlu verify email authentication setiap commit?

Tidak perlu setiap commit. Frequency yang masuk akal adalah: sebelum setiap deployment ke production, dan sekali sehari di branch development sebagai scheduled job. Overhead dari running DNS lookup sangat kecil (kurang dari 1 detik), jadi running lebih sering tidak masalah. Yang lebih penting adalah coverage: pastikan every deployment melalui pipeline ini.

Tools apa saja yang bisa dipakai untuk automated DNS lookup selain dig?

Selain dig, Anda bisa pakai drill (bagian dari ldns), host command, atau nslookup. Untuk programmatic access, Python dengan library dnspython memberikan kontrol paling fleksibel. Di GitHub Actions, ada juga action marketplace seperti mac-infinity/dns-lookup@v1 yang abstract away installation steps. Untuk basic checks, kombinasi bash dan dig sudah cukup dalam majority kasus.

Bagaimana kalau SMTP provider mengganti infrastruktur mereka tanpa memberitahu?

Ini adalah masalah umum yang tidak punya solusi teknis murni. Hubungan dengan SMTP provider harus include clear communication channel untuk infrastructure changes. Sebagai mitigasi, set up monitoring di luar pipeline: scheduled job yang cek email authentication records secara periodik dan alert kalau ada perubahan. Pipeline yang hanya jalan saat deployment tidak akan mendeteksi perubahan yang terjadi di luar deployment window.


Kalau tim Anda butuh SMTP infrastructure yang sudah termasuk authentication setup yang benar dan monitoring built-in, KIRIM.EMAIL Dev menyediakan plug and play SMTP untuk kebutuhan Anda. Dengan managed IP dan server di Indonesia, Anda bisa fokus ke development sementara email authentication di-maintain secara otomatis.


Hasbi Putra adalah Head of Marketing di KIRIM.EMAIL, email delivery infrastructure untuk developer dan tim IT di Indonesia. KIRIM.EMAIL mengirim lebih dari 11 juta email per hari dengan server yang sepenuhnya berlokasi di Indonesia.

Leave a Comment

Your email address will not be published. Required fields are marked *