Saya sudah kehilangan satu tersangka sebelum benar-benar mulai menyelidiki. Semua email yang keluar dari situs mendarat di folder spam Gmail, dan tuduhan pertama saya jatuh ke template HTML-nya: tabel selebar 600 piksel, gaya inline di mana-mana, satu gambar yang dimuat dari server jauh. Barang seperti itu memang gampang dicurigai. Masalahnya, versi lama email yang sama, yang masih teks polos tanpa satu tag pun, juga mendarat di spam. Templatnya bebas dari tuduhan, dan yang salah ada di lapisan lebih bawah.
Satu-satunya cara melihat lapisan itu adalah membuka header mentah pesan yang benar-benar sampai. Di sana ada dua penilaian yang saling bertentangan, dicetak berdekatan oleh server penerima yang sama:
Return-Path: <noreply@srv0000.hosting-bersama.example>
Received: from srv0000.hosting-bersama.example (unknown [198.51.100.42])
dkim=none;
spf=pass smtp.mailfrom=noreply@srv0000.hosting-bersama.example;
dmarc=fail reason="SPF not aligned (relaxed), No valid DKIM"
header.from=situs-klien.example
Hostname, IP dan nama domain di atas saya samarkan, tapi bentuknya persis seperti aslinya. Satu baris bilang lulus, satu baris lagi bilang gagal, dan keduanya benar.
spf=pass menjawab pertanyaan yang tidak saya ajukan
Sebuah email membawa dua alamat pengirim yang berbeda, dan gampang sekali menganggapnya satu. Yang pertama alamat amplop, yang muncul sebagai Return-Path dan sebagai smtp.mailfrom di hasil pemeriksaan. Itu alamat yang dipakai antar server untuk urusan teknis seperti mengembalikan pesan yang gagal terkirim. Yang kedua alamat di header From:, dan itulah satu-satunya yang dilihat penerima di kotak masuknya.
SPF hanya memeriksa yang pertama. Pertanyaannya sempit: apakah IP yang barusan menyerahkan pesan ini diizinkan mengirim atas nama domain amplopnya? Di situs ini, email dikirim lewat fungsi mail bawaan PHP di hosting bersama, jadi domain amplopnya adalah hostname server itu sendiri. Server tentu saja berhak mengirim atas nama dirinya sendiri. Jadi spf=pass di situ nyaris tidak mungkin gagal, dan sama sekali tidak berkata apa pun tentang domain situs klien.
DKIM tidak ikut bermain sama sekali. dkim=none berarti tidak ada tanda tangan yang bisa diperiksa, yang wajar karena tidak ada yang menandatangani apa pun di jalur ini.
DMARC yang menagih sisanya. Dia tidak puas dengan sekadar kelulusan, dia menuntut kesejajaran: SPF atau DKIM harus lulus untuk domain yang sama dengan yang tertulis di From:. Mode relaxed sedikit lebih longgar karena membandingkan domain organisasi, bukan hostname persis, tapi kelonggaran itu tidak menolong ketika yang dibandingkan adalah domain penyedia hosting lawan domain situs. Jadi SPF lulus di tempat yang salah, DKIM tidak ada, dan DMARC gagal. Alasannya bahkan ditulis lengkap di headernya, tinggal dibaca.
Jalan pintas yang kelihatan murah, dan kenapa saya membuangnya
Perbaikan pertama yang terpikir cuma butuh satu opsi. Paksa Return-Path memakai alamat di domain situs, maka smtp.mailfrom dan header.from jadi domain yang sama, kesejajaran terpenuhi, DMARC senang. Tidak perlu kotak surat baru, tidak perlu layanan baru.
Untung saya mengujinya dulu, karena logikanya berlubang. Kesejajaran memang salah satu syarat DMARC, tapi bukan satu-satunya. Pemeriksaan yang disejajarkan itu tetap harus lulus. Begitu Return-Path pindah ke domain situs, yang dipakai untuk menilai bukan lagi catatan SPF milik server hosting, melainkan catatan SPF milik domain situs, dan pertanyaannya berubah jadi apakah IP server web ini ada di dalamnya.
Catatan SPF domain itu isinya beberapa include:, dan include: tidak berisi IP, dia menunjuk ke catatan lain yang bisa menunjuk lagi ke catatan berikutnya. Jadi tidak bisa dijawab dengan melihat satu baris.
dig +short TXT situs-klien.example
"v=spf1 include:_spf.penyedia.example include:relay.penyedia.example ~all"
Saya menulis skrip kecil yang menelusuri rantai itu secara rekursif dan mengumpulkan mekanisme ip4 di ujung-ujungnya, sampai tidak ada include: tersisa. Hasil akhirnya lima blok: dua buah /24, satu /22, satu /20, dan satu /32 yang menunjuk tepat satu host. IP server web yang selama ini mengirim tidak ada di satu blok pun.
Artinya jalan pintas tadi bukan perbaikan setengah, tapi kemunduran. Keadaan sekarang adalah lulus tapi tidak sejajar. Kalau Return-Path dipaksa sejajar, keadaannya berubah jadi spf=fail terang-terangan, dan penerima punya penolakan eksplisit untuk dipegang, bukan sekadar ketiadaan bukti. Memenuhi satu syarat sambil merusak syarat yang lain menghasilkan sesuatu yang lebih buruk daripada titik awalnya.
Perbaikannya: kirim dari kotak surat yang benar-benar ada di domain itu
Karena tidak ada cara membuat server web itu sah mengirim atas nama domain situs, saya berhenti memaksanya. Pengiriman dipindahkan ke SMTP terautentikasi lewat WP Mail SMTP, memakai kotak surat sungguhan di domain situs, port 465 dengan SSL, autentikasi menyala, dan nama serta alamat pengirim diisi alamat kotak surat itu juga.
Satu opsi yang saya nyalakan dan sering dilewati orang adalah Force From Email. Tanpa itu, plugin mana pun bebas menetapkan alamat From: versinya sendiri saat mengirim, dan satu plugin yang menulis alamat di domain lain sudah cukup untuk menjatuhkan kembali kesejajaran yang barusan susah payah dibangun. Force From Email membuat pengirimnya seragam apa pun yang diminta kode di atasnya.
Setelah itu semuanya jatuh ke tempatnya dengan sendirinya. SPF dinilai terhadap server milik penyedia surat untuk domain situs, yang memang berhak, dan alamat amplop serta alamat header ada di domain yang sama karena berasal dari kotak surat yang sama. Kesejajarannya bukan hasil tambalan, melainkan konsekuensi dari cara pengirimannya.
Urutan penyalaannya memakan waktu lebih lama daripada perbaikannya
Ada satu opsi bernama Return Path di panel yang sama, dan opsi inilah yang paling lama menyita waktu saya. Aturannya: dia harus mati selama mailer PHP masih aktif, dan baru dinyalakan setelah SMTP asli benar-benar jalan.
Alasannya persis jalan pintas yang tadi saya buang. Selama mailer PHP yang aktif, opsi itu memaksa envelope sender ke alamat di domain situs, yang tidak diizinkan catatan SPF domain itu untuk IP server web. Jadi kalau dinyalakan terlalu awal, keadaan yang tadinya lulus tapi tidak sejajar langsung turun jadi gagal, tepat di saat saya merasa sedang menyiapkan perbaikan. Konfigurasi yang benar di keadaan akhir belum tentu aman di keadaan tengah.
Ada satu jebakan kecil lagi di panel itu yang membuat saya sempat mengira pengaturannya tidak tersimpan. Saat menyimpan dengan mailer PHP masih terpilih, plugin memunculkan dialog konfirmasi berbasis jQuery, dan tidak ada apa pun yang benar-benar tersimpan sampai tombol konfirmasinya diklik. Beberapa buntu yang saya catat sebagai "pengaturannya tidak nempel" ternyata dialog itu saja, berulang kali.
Buktinya jangan diambil dari alat yang saya kendalikan sendiri
Tombol tes bawaan plugin memang berguna, tapi cakupannya sempit. Kalau kredensial SMTP-nya salah, dia gagal dengan error autentikasi, jadi lulusnya membuktikan koneksi dan login benar. Dia tidak membuktikan apa pun tentang di folder mana pesannya mendarat, dan itulah pertanyaan yang sedang saya kejar.
Jadi verifikasinya saya lakukan lewat submisi form beneran. Di sini ada jebakan yang khas buatan sendiri: form itu punya dua pesan sukses yang bunyinya mirip dan artinya berlawanan. Yang satu berarti pesannya benar-benar dikirim, yang satu lagi adalah tanggapan untuk honeypot dan jebakan waktu, yang sengaja terlihat sukses padahal tidak ada apa pun yang dikirim. Kalau salah membaca yang mana yang kembali, saya bisa merayakan perbaikan yang belum tentu terjadi. Jadi baca kalimatnya, bukan cuma warnanya.
Bukti terbaiknya justru datang gratis dan tidak saya rencanakan. Salinan yang diterima akhirnya menampilkan logo di badan emailnya, padahal sebelumnya kotak itu kosong. Gmail hanya memuat gambar dari server jauh untuk surat yang tidak dianggapnya spam. Jadi logo yang tampil itu bukan sekadar kosmetik, melainkan penilaian dari sisi penerima yang tidak bisa saya sentuh, tidak bisa saya konfigurasi, dan tidak bisa saya bujuk.
Baris hijau itu jawaban atas pertanyaannya sendiri
Yang membuat kasus ini memakan waktu bukan kesulitan teknisnya, tapi satu kata di header yang saya baca sebagai vonis padahal cuma keterangan. SPF, DKIM dan DMARC bertanya tiga hal berbeda, dan hanya yang ketiga yang peduli pada domain yang dilihat penerima. Lulus di dua yang pertama tidak berarti apa-apa kalau lulusnya di tempat yang tidak ditanyakan.
Dari kasus ini saya bawa dua kebiasaan. Pertama, sebelum menambal kesejajaran, urai dulu rantai SPF sampai daftar IP terakhirnya dan periksa apakah pengirimmu benar-benar tercakup, karena separuh perbaikan bisa mendarat lebih rendah daripada tidak memperbaiki sama sekali. Kedua, cari bukti yang alat ujimu sendiri tidak sanggup memalsukan. Tombol tes menjawab pertanyaan yang saya susun sendiri. Gambar yang akhirnya dimuat oleh penerima menjawab pertanyaan yang sebenarnya.