Mert Özen Her Satırda Daha İleri
DevOps Domain, DNS ve CDN — #03

E-posta için DNS: MX, SPF, DKIM ve DMARC ile Bookstore'un Mail'lerinin Spam'e Düşmemesi

Mert Özen 14 Kas 2026 14 dk 1 görüntülenme
E-posta için DNS: MX, SPF, DKIM ve DMARC ile Bookstore'un Mail'lerinin Spam'e Düşmemesi

Bir mail'in spam'e düşmemesi için SPF veya DKIM'in geçmesi yetmez; DMARC, geçen domain'in kullanıcının gördüğü From: domain'iyle hizalı olmasını ister ve Gmail, Yahoo ve Outlook bunu toplu göndericilerden şart koşuyor. Laboratuvarda Bookstore'un MX, SPF, DKIM ve DMARC kayıtlarını kuruyor, gerçek kütüphanelerle bir sipariş mail'ini imzalayıp doğruluyor, 10 sorgu limitinin yalnızca bazı IP'lerde kırıldığını ve sağlayıcının varsayılan DKIM'inin neden DMARC'ı geçemediğini görüyoruz.

Sürüm notu: Bu yazıdaki örnekler BIND (açık kaynak DNS sunucusu) 9.20 ve dig 9.20 ile, e-posta kimlik doğrulama değerlendirmeleri Python 3.13 üzerinde pyspf 2.0.14, dkimpy 1.1.8 ve authheaders 0.16.3 ile Docker 29'daki laboratuvarda denenmiştir; e-posta sağlayıcılarının kuralları 14 Kasım 2026'da resmi sayfalarından okunmuştur.

Önce Tahmin Et

Sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı), sipariş onayı ve parola sıfırlama mail'lerini bir transactional e-posta sağlayıcısı (uygulamanın API ile mail gönderdiği servis) üzerinden gönderiyor. Ekibin kendi mail kutuları ise ayrı bir kurumsal mail servisinde. Sağlayıcının kurulum sihirbazı "SPF (domain adına mail gönderebilecek IP'lerin listesi) kaydınıza include:_spf.txprovider.example ekleyin" demiş; Emre de bunu yapmış. Gönderim çalışıyor, ama Gül'ün müşteri hizmetleri kutusuna "sipariş onayım spam'e düştü" şikâyetleri geliyor.

Tahmin et: SPF kaydı doğruysa ve sağlayıcı mail'leri DKIM (mail'in domain adına atılan kriptografik imzası) ile imzalıyorsa, alıcı sunucu bu mail'e neden güvenmiyor olabilir? İpucu: Gmail'in gösterdiği "gönderen" ile SPF'in ve DKIM'in kontrol ettiği domain aynı şey değil. Bu yazıda MX (gelen mail sunucusu kaydı), SPF, DKIM ve DMARC (From: adresini SPF ve DKIM sonucuyla eşleştiren politika) kayıtlarını laboratuvarda kurup bir mail'i gerçekten imzalayıp doğrulayacağız; cevap alignment kavramında çıkacak.

Senaryo ve Hedef Kurulum

Bookstore'un iki ayrı mail akışı var ve bunları ayırmak bu yazının ana kararı:

AkışGönderen adresAltyapıDomain
Kurumsal mail (ekip, destek)ayse@bookstore.exampleKurumsal mail servisibookstore.example
Transactional mail (sipariş, parola)orders@bookstore.exampleTransactional sağlayıcıEnvelope ve DKIM: mail.bookstore.example

Transactional mail'in görünen adresi yine bookstore.example'da, ama teknik kimliği (bounce adresi ve DKIM imzası) mail.bookstore.example alt domain'inde. Bunun sebebi itibar: transactional akışta bir sorun olursa (bir bug yüzünden binlerce mail, yüksek şikâyet oranı) etkisi kurumsal mail'in itibarına daha az yansır. SPF ve DKIM kayıtları da ayrı yerlerde durur, birinin değişikliği diğerini bozmaz. Laboratuvardaki zone'un mail kısmı:

; mail
@               IN MX    10 mx1.corpmail.example.
@               IN MX    20 mx2.corpmail.example.
@               IN TXT   "v=spf1 include:_spf.corpmail.example -all"
_dmarc          IN TXT   "v=DMARC1; p=none; rua=mailto:dmarc-reports@bookstore.example"

mail            IN MX    10 feedback.txprovider.example.
mail            IN TXT   "v=spf1 include:_spf.txprovider.example -all"
s1._domainkey.mail IN TXT ( "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2rU8UK0/E30OMTz0FfG6xSj4OkB4X3BV5yoEr/czlZ1GijiMrbV9pJ8hy+9Oq+tMm8gBthy2cwZs605sA+kj+X2DhWLWgF7e0D+LxrLSUEL3JtfnBsGyR8JU6vKZ9ByG48EowUIX1V4OmrG7GaWt6Zei0oPA"
                            "K4ryXlatZ1uPX5oSxA/xRfkIbu2VvJqk8e3Z0x1yzafQkY3804yeJAwkHwI1LZYH/AkB2IYkGYLf8HXvDNbz4UsEvTunxSnNVeMtsuMO6QztcjKihB+VWC8MnzSDcfTvXe3Z5B3JcaP0366G4V3+hTrgkLBuWil9zaUlvFmsc2tC51Yfmg9XUK0AVQIDAQAB" )

Laboratuvarda kurumsal mail servisini corpmail.example, transactional sağlayıcıyı txprovider.example adlı iki zone temsil ediyor; ikisi de aynı DNS container'ında. Değerlendirmeleri ise aynı Docker ağındaki bir Python container'ında, gerçek SPF/DKIM/DMARC kütüphaneleriyle yaptım. Container'ın resolver'ı laboratuvardaki DNS sunucusuna yönlendirildi.

MX: Mail Nereye Gelir?

MX (mail exchanger; domain'e gelen mail'i hangi sunucunun kabul edeceğini söyleyen kayıt) yalnızca gelen mail'i ilgilendirir. Gönderim tarafında hiçbir rolü yok; bu yüzden "MX'i değiştirdik, gönderim bozuldu" diye bir şey olmaz, ama "MX'i değiştirdik, gelen mail'ler kayboldu" çok olur. Sayı tercih sırasıdır: önce 10'a, ulaşılamazsa 20'ye gidilir. MX kaydı bir hostname'e işaret etmeli; IP adresi veya CNAME (takma ad kaydı) yazmak RFC'lere (internet standart dokümanları) aykırı ve bazı sunucular bunu reddeder.

mail alt domain'indeki MX ilginç: kimse @mail.bookstore.example'a mail yazmayacak. Ama transactional sağlayıcı envelope sender'ı (SMTP'deki MAIL FROM, yani bounce'ların döneceği adres) bounces@mail.bookstore.example gibi bir adres yapıyor ve geri dönen mail'leri işleyebilmek için bu alt domain'in MX'inin kendi sunucusunu göstermesini istiyor. Sağlayıcının kurulum ekranındaki "custom return-path" veya "custom MAIL FROM" adımı tam olarak bu.

SPF: Bu IP Benim Adıma Gönderebilir mi?

SPF bir TXT (serbest metin) kaydıdır ve envelope sender'ın domain'ine bakar, mail'in içinde görünen From: adresine değil. Alıcı sunucu, bağlantının geldiği IP'yi bu listeyle karşılaştırır:

import dkim, spf
from authheaders import authenticate_message

def message(sender: str, body: str) -> bytes:
    return (f"From: Bookstore <{sender}>\r\n"
            "To: Ada <ada@example.com>\r\n"
            "Subject: Order BK-1042 confirmed\r\n"
            "Date: Sat, 14 Nov 2026 10:15:00 +0000\r\n"
            "Message-ID: <bk-1042@mail.bookstore.example>\r\n"
            "\r\n" + body).encode()

def sign(msg: bytes, selector: str, domain: str, key_file: str) -> bytes:
    key = open(key_file, "rb").read()
    header = dkim.sign(msg, selector.encode(), domain.encode(), key,
                       include_headers=[b"From", b"To", b"Subject", b"Date", b"Message-ID"])
    return header + msg

print("SPF")
for ip, sender, helo in [("198.51.100.25", "bounces@mail.bookstore.example", "mta1.txprovider.example"),
                         ("203.0.113.10", "bounces@mail.bookstore.example", "web1.bookstore.example"),
                         ("203.0.113.130", "ayse@bookstore.example", "mx1.corpmail.example")]:
    result, explanation = spf.check2(i=ip, s=sender, h=helo)
    print(f"  {ip:<15} {sender:<32} -> {result}")
SPF
  198.51.100.25   bounces@mail.bookstore.example   -> pass
  203.0.113.10    bounces@mail.bookstore.example   -> fail
  203.0.113.130   ayse@bookstore.example           -> pass

İkinci satır gerçek bir hikâye: Bookstore.Api'yi çalıştıran sunucu (203.0.113.10) bir gün "hızlı çözüm" olarak mail'i sağlayıcı yerine doğrudan SMTP ile göndermeye başlarsa, SPF fail verir. Kaydın sonundaki -all "listede olmayan her IP reddedilmeli" demek; ~all ise softfail, yani "şüpheli say ama reddetme". DMARC kurulu bir domain'de bu ikisinin pratik farkı azdır, çünkü son kararı DMARC verir; DMARC olmayan alıcılarda ise -all daha net bir sinyaldir. Bookstore'un gönderen bütün kaynakları bilindiği için -all kullanıyoruz.

SPF'in iki kuralı neredeyse her ekipte bir kez kırılır. Birincisi: bir isimde yalnızca bir SPF kaydı olabilir. Yeni bir servis eklenirken "bir TXT daha ekleyelim" denirse sonuç:

$ dig @192.0.2.20 two-spf.bookstore.example TXT +short
"v=spf1 include:_spf.corpmail.example -all"
"v=spf1 include:_spf.txprovider.example -all"
$ python -c "import spf; print(spf.check2(i='198.51.100.25', s='orders@two-spf.bookstore.example', h='mta1.txprovider.example'))"
('permerror', 'SPF Permanent Error: Two or more type TXT spf records found.')

İki kaydı birleştirmek gerekir: v=spf1 include:_spf.corpmail.example include:_spf.txprovider.example -all. İkincisi daha sinsi: SPF değerlendirmesi en fazla 10 DNS sorgusu yapabilir (RFC 7208). include, a, mx, exists ve redirect her biri bir sorgu sayılır; ip4/ip6 sayılmaz. Sorun şu ki her include'un içinde başka include'lar olabilir ve bunlar başkasının kontrolünde. Bir yıl içinde CRM (müşteri ilişkileri yönetimi aracı), destek aracı ve bülten servisi eklenmiş bir kaydı ayrı bir isimde denedim ve include ağacını sayan bir script yazdım:

#!/bin/bash
# Kullanım: spf-lookups.sh <domain> [resolver]
# SPF include ağacını yazdırır ve DNS sorgusu yapan terimleri sayar (sınır: 10).
resolver=${2:+@$2}
total=0
walk() {
    local domain=$1 indent=$2 record term
    record=$(dig $resolver +short "$domain" TXT | tr -d '"' | grep '^v=spf1')
    if [[ $(grep -c . <<<"$record") -gt 1 ]]; then
        echo "${indent}$domain: MORE THAN ONE SPF RECORD (permerror)"; return
    fi
    echo "${indent}$domain"
    for term in $record; do
        case "$term" in
            include:*|redirect=*)
                total=$((total + 1)); walk "${term#*[:=]}" "$indent  " ;;
            a|a:*|mx|mx:*|ptr|ptr:*|exists:*)
                total=$((total + 1)); echo "$indent  $term" ;;
        esac
    done
}
walk "$1" ""
echo "DNS lookups: $total/10"
(( total <= 10 ))
$ ./spf-lookups.sh google.com 1.1.1.1
google.com
  _spf.google.com
DNS lookups: 1/10
$ ./spf-lookups.sh spf-2027.bookstore.example 192.0.2.20
spf-2027.bookstore.example
  _spf.corpmail.example
  _spf.txprovider.example
  _spf.crm.saas.example
    _spf1.crm.saas.example
    _spf2.crm.saas.example
  _spf.helpdesk.saas.example
    _netblocks1.helpdesk.saas.example
    _netblocks2.helpdesk.saas.example
    _netblocks3.helpdesk.saas.example
  _spf.newsletter.saas.example
    a
    mx
    _spf1.newsletter.saas.example
DNS lookups: 13/10

Kayıt yalnızca beş include içeriyor ama toplam 13 sorgu yapıyor. Asıl şaşırtıcı kısım ise alıcı tarafında:

$ python -c "
import spf
for ip in ['198.51.100.25', '198.51.100.130', '198.51.100.241', '192.0.2.99']:
    print(ip, spf.check2(i=ip, s='orders@spf-2027.bookstore.example', h='mta1.txprovider.example'))"
198.51.100.25 ('pass', 'sender SPF authorized')
198.51.100.130 ('pass', 'sender SPF authorized')
198.51.100.241 ('permerror', 'SPF Permanent Error: Too many DNS lookups')
192.0.2.99 ('permerror', 'SPF Permanent Error: Too many DNS lookups')

SPF soldan sağa değerlendirilir ve eşleşme bulununca durur. Transactional sağlayıcının IP'si ikinci include'da eşleştiği için limite hiç ulaşılmıyor ve pass geliyor. Bülten servisinin IP'si ise ağacın sonunda; oraya varmadan sorgu sayısı 10'u geçiyor ve sonuç permerror. Yani SPF'in "bazı mail'lerde" bozulduğu, testlerde ise her şeyin yeşil göründüğü bir durum. Çözüm, kullanılmayan servisleri kayıttan çıkarmak, gönderim akışlarını alt domain'lere bölmek (her alt domain'in kendi 10 sorguluk bütçesi var) ve sağlayıcının izin veriyorsa include yerine IP aralıklarını yazmak. Son seçenek riskli: sağlayıcı IP değiştirdiğinde senin kaydın eskimiş olur.

DKIM: Mail'i Domain Adına İmzalamak

DKIM (DomainKeys Identified Mail; gönderen sunucunun mail'i private key ile imzalaması ve public key'in DNS'te yayımlanması) mail'in yolda değişmediğini ve imzayı atan domain'in onu gönderdiğini kanıtlar. Public key <selector>._domainkey.<domain> adındaki bir TXT'de durur. Selector, aynı domain'de birden fazla anahtarın yan yana yaşamasını sağlar. Laboratuvarda openssl genrsa -out s1.private.pem 2048 ile 2048 bit bir RSA (yaygın bir public key algoritması) anahtarı ürettim (Google 1024 bit'i alt sınır, 2048'i önerilen değer olarak yazıyor), public key'i s1._domainkey.mail.bookstore.example'a koydum ve bir sipariş onayını imzaladım. 255 karakterden uzun TXT değerleri zone dosyasında yukarıdaki gibi tırnaklı parçalara bölünür; DNS onları uç uca ekleyip tek değer olarak okur. Script'in devamı:

body = "Your order BK-1042 (Dune, Emma) is confirmed. Total: 640.00 TRY\r\n"
signed = sign(message("orders@bookstore.example", body), "s1", "mail.bookstore.example", "/work/s1.private.pem")
print(b"\r\n".join(signed.split(b"\r\n")[:3]).decode())
print("DKIM verify (original):", dkim.verify(signed))
tampered = signed.replace(b"640.00", b"6.40")
print("DKIM verify (body changed):", dkim.verify(tampered))
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple;
 d=mail.bookstore.example; i=@mail.bookstore.example; q=dns/txt; s=s1;
 t=1794621927; h=from : to : subject : date : message-id;
DKIM verify (original): True
DKIM verify (body changed): False

d= imzayı atan domain, s= selector, h= imzaya dahil header'lar. Doğrulayan taraf s1._domainkey.mail.bookstore.example'ı DNS'ten okuyup imzayı kontrol etti. Mail'in gövdesinde tutarı 640.00'dan 6.40'a çevirince imza kırıldı. Gerçek hayatta DKIM'i en sık kıran şey saldırgan değil, araya giren sistemler: mail'in altına "bu mail virüs taramasından geçti" ekleyen bir gateway veya konu satırına [EXTERNAL] yazan bir kural.

Key rotation için selector'lar var. Transactional sağlayıcıların çoğu DKIM'i s1._domainkey'e bir CNAME ile kendi sunucularına yönlendirmeni ister; böylece anahtarı onlar döndürür. Anahtarı kendin yönetiyorsan sıra şu: yeni selector'ı (s2) DNS'e ekle, TTL (kaydın cache süresi) kadar bekle, imzalamayı s2'ye geçir, yolda olan mail'ler doğrulanabilsin diye s1'i birkaç gün daha tut, sonra s1'in p= değerini boşalt. RFC 6376'ya göre boş p= "bu anahtar iptal edildi" demek.

DMARC ve Alignment: Önce Tahmin Et'in Cevabı

SPF ve DKIM'in ortak bir eksiği var: ikisi de kullanıcının gördüğü From: adresine bakmaz. SPF envelope sender'ın domain'ini, DKIM d='deki domain'i doğrular. Bir dolandırıcı kendi domain'inden SPF ve DKIM'i geçen bir mail atıp From:'a orders@bookstore.example yazabilir. DMARC (Domain-based Message Authentication, Reporting and Conformance; From: domain'inin SPF veya DKIM ile eşleşmesini şart koşan politika kaydı) bu boşluğu kapatır: SPF veya DKIM'den en az biri geçmeli ve geçtiği domain From: domain'iyle hizalı (aligned) olmalı. Relaxed alignment'ta aynı organizasyonel domain yeterli (mail.bookstore.example ile bookstore.example hizalı), strict alignment'ta (aspf=s, adkim=s) birebir aynı olmalı.

Önce Tahmin Et'teki durumu laboratuvarda kurdum. Birinci mail Bookstore'un kendi DKIM anahtarı ve kendi bounce adresiyle, ikincisi sağlayıcının varsayılan ayarlarıyla gidiyor: sağlayıcı mail'i kendi domain'iyle imzalıyor ve bounce adresi de kendi domain'inde:

def results(msg: bytes, mail_from: str) -> str:
    return authenticate_message(msg, "mx.example.com", ip="198.51.100.25", mail_from=mail_from,
                                helo="mta1.txprovider.example", spf=True, dkim=True, dmarc=True)

print("Own domain, aligned:")
print("  " + results(signed, "bounces@mail.bookstore.example"))
provider_signed = sign(message("orders@bookstore.example", body), "tx1", "txprovider.example", "/work/tx1.private.pem")
print("Provider defaults, not aligned:")
print("  " + results(provider_signed, "bounces@txprovider.example"))
Own domain, aligned:
  Authentication-Results: mx.example.com; spf=pass reason="sender SPF authorized" smtp.helo=mta1.txprovider.example smtp.mailfrom=mail.bookstore.example; dkim=pass header.d=mail.bookstore.example header.i=@mail.bookstore.example; dmarc=pass (Used From Domain Record) header.from=bookstore.example policy.dmarc=none
Provider defaults, not aligned:
  Authentication-Results: mx.example.com; spf=none smtp.helo=mta1.txprovider.example smtp.mailfrom=bounces@txprovider.example; dkim=pass header.d=txprovider.example header.i=@txprovider.example; dmarc=fail (Used From Domain Record) header.from=bookstore.example policy.dmarc=none

Cevap ikinci satırda. DKIM pass, ama imzayı atan txprovider.example, kullanıcının gördüğü ise bookstore.example; hizalı değiller ve DMARC fail. Emre'nin SPF'e eklediği include bu senaryoda hiçbir işe yaramıyor, çünkü envelope sender da sağlayıcının domain'inde ve Bookstore'un SPF kaydına hiç bakılmıyor. Politika p=none olduğu için mail reddedilmedi, ama Gmail ve Outlook toplu göndericilerden DMARC'ın geçmesini bekliyor; hizasız mail spam klasörünün en iyi adayı. Düzeltme sağlayıcının panelinde: "custom DKIM" (veya "domain authentication") ile d=mail.bookstore.example imzası ve "custom return-path" ile bounce adresi. İlk satır bu iki ayarın sonucu.

Laboratuvarla ilgili bir not: alignment hesabı "organizasyonel domain"i bulmak için Public Suffix List'i (co.uk, com.tr gibi kayıt uzantılarının listesi) kullanıyor. Dokümantasyon için ayrılmış .example bu listede olmadığından kütüphane bookstore.example ile txprovider.example'ı aynı organizasyon sandı ve ilk denemede ikinci mail'e de pass verdi. Listeye example satırını ekleyince sonuç düzeldi. Mayıs 2026'da yayımlanan ve RFC 7489'un yerini alan RFC 9989, organizasyonel domain'i bu liste yerine DNS ağacında yukarı doğru yürüyerek bulmayı tanımlıyor; bu sorun da tam olarak bu yüzden var.

DMARC'ı Kademeli Sıkılaştırmak ve Raporları Okumak

DMARC kaydının en önemli alanı p: none (sadece raporla), quarantine (spam'e at) veya reject (reddet). rua, alıcıların günlük toplu raporları (aggregate report) göndereceği adres. Bu raporlar XML ve her satırında "şu IP'den, şu kadar mail, SPF şu, DKIM şu, alignment şu" bilgisi var. Senin domain'in adına mail gönderen ve unuttuğun her şeyi (eski bir bülten aracı, muhasebenin fatura sistemi, bir ofis yazıcısının tarama mail'i) bu raporlardan öğrenirsin. Ham XML'i okumak zor; açık kaynak parser'lar veya ticari DMARC raporlama servisleri bunu tabloya çevirir. Rapor adresi başka bir domain'deyse (örneğin bir raporlama servisi), o domain'in bookstore.example._report._dmarc.<servis-domain'i> adında bir TXT ile raporları kabul ettiğini beyan etmesi gerekir; yoksa alıcılar raporu göndermez.

Bookstore için takvim:

  • 1. hafta: p=none; rua=... ile başla. Hiçbir mail etkilenmez, sadece rapor gelir.
  • 2-4. hafta: Raporlardaki her kaynak için ya DKIM/SPF'i düzelt ya da o kaynağı kapat. Hedef, meşru trafiğin tamamının hizalı geçmesi.
  • 5. hafta: p=quarantine. RFC 9989 ile eski pct alanı kaldırıldı; yerine t=y (test modu) geldi. t=y varken alıcı politikayı bir seviye hafif uygular: quarantine fiilen none gibi, reject fiilen quarantine gibi davranır. Ara adım olarak kullanılabilir. Kayıt hâlâ v=DMARC1 ile başlıyor ve eski kayıtlar geçerli.
  • Birkaç hafta sorunsuz geçince: p=reject. Alt domain'ler için sp= ile ayrı politika verilebilir; mail.bookstore.example zaten hizalıysa aynı politikayı alabilir.

Büyük Sağlayıcıların Toplu Gönderici Kuralları

Bu işler artık "iyi olur" değil. Gmail ve Yahoo Şubat 2024'ten, Microsoft (Outlook.com, Hotmail, Live) 5 Mayıs 2025'ten beri kişisel kutulara günde 5.000'den fazla mail gönderenlerden şunları istiyor: SPF ve DKIM'in ikisi birden, en az p=none olan ve geçen bir DMARC kaydı ve From: domain'inin SPF veya DKIM domain'iyle hizalı olması. Gmail ve Yahoo ayrıca şikâyet oranının %0,3'ün altında kalmasını, gönderen IP'lerin geçerli PTR (IP'den isme ters DNS kaydı) kayıtlarına sahip olmasını ve pazarlama mail'lerinde tek tıkla abonelikten çıkma (List-Unsubscribe-Post header'ı, RFC 8058) istiyor. Google, şikâyet oranının %0,1'in altında tutulmasını öneriyor. Microsoft'un sayfasına göre kurallara uymayan mail önce junk klasörüne gidiyor, sorun sürerse 550 5.7.515 koduyla reddediliyor. Sipariş onayı ve parola sıfırlama gibi transactional mail'ler abonelikten çıkma şartının dışında; ama kimlik doğrulama şartları onlar için de geçerli.

"Günde 5.000" eşiği Bookstore'u ilk aylarda kapsamayabilir. Yine de kuralları baştan uygula: eşik "herhangi bir gün" üzerinden sayılıyor, kampanya yapılan bir günde aşılabilir. Ayrıca bu ayarlar küçük göndericilerde de teslim oranını artırıyor.

Üretimde Dikkat

Gönderim akışını alt domain'e ayırmak tek başına yetmez; itibar da ayrı yönetilmeli. Transactional ve pazarlama mail'lerini ayrı alt domain'lerden (mail. ve news. gibi) gönder. Bir bülten kampanyasının yüksek şikâyet oranı, parola sıfırlama mail'lerinin spam'e düşmesine yol açmasın.

Mail göndermeyen domain'leri kilitle. Bookstore'un sadece marka koruması için aldığı bookstore.com.tr gibi domain'ler sahtecilerin en sevdiği adreslerdir, çünkü kimse onları izlemez. example.com bunun nasıl yapılacağını gösteriyor:

$ dig @1.1.1.1 example.com TXT +short
"v=spf1 -all"
"_k2n1y4vw3qtb4skdx9e7dxt97qrmmq9"
$ dig @1.1.1.1 example.com MX +short
0 .
$ dig @1.1.1.1 _dmarc.example.com TXT +short
"v=DMARC1;p=reject;sp=reject;adkim=s;aspf=s"

v=spf1 -all "bu domain'den hiçbir IP mail gönderemez", MX 0 . (null MX, RFC 7505) "bu domain mail kabul etmez", DMARC p=reject ise "hizasız her şeyi reddet" diyor. Üçü birlikte, mail kullanmayan bir domain için doğru varsayılan.

DNS değişikliklerini mail ekibiyle birlikte yap. SPF'i değiştirmek, DKIM selector'ını silmek veya MX'i taşımak, web tarafındaki bir değişiklikten farklı olarak hemen fark edilmez; sorun ancak alıcıların raporlarında veya müşteri şikâyetlerinde ortaya çıkar. DMARC raporlarını izleyen biri olmalı.

Sağlayıcının DNS isteklerini olduğu gibi kopyalamadan önce oku. Bazı sağlayıcılar hâlâ apex'e kendi include'larını eklemeni ister, oysa gönderimi alt domain'e taşıdıysan bu gereksiz ve 10 sorguluk bütçeden yer.

Sık Düşülen Tuzaklar

İkinci bir SPF kaydı eklemek: Yeni bir servis için ayrı bir v=spf1 TXT eklemek, yukarıda gördüğümüz gibi permerror ile iki kaydı birden geçersiz kılar. Her zaman mevcut kaydı düzenle.

Mail göndermiyoruz diye hiçbir şey yapmamak: SPF ve DMARC'ı olmayan bir domain, başkasının senin adınla mail göndermesine açık. Gönderim yoksa null SPF ve p=reject koy.

Alignment'ı unutup "SPF geçiyor" demek: SPF'in geçmesi, DMARC'ın geçtiği anlamına gelmez. Authentication-Results header'ında dmarc= sonucuna ve header.from ile header.d/smtp.mailfrom'un hizasına bak.

Doğrudan p=reject ile başlamak: Raporlara bakmadan reject'e geçmek, unutulmuş bir sistemin (fatura, destek aracı) mail'lerinin bir gecede kaybolması demek. none ile başla, raporları oku, sonra sıkılaştır.

DKIM anahtarını hiç döndürmemek: 2015'te üretilmiş 1024 bit bir anahtar hâlâ birçok domain'de duruyor. Anahtarı yılda bir döndürmeyi takvime koy veya sağlayıcının CNAME tabanlı otomatik rotation'ını kullan.

Küçük Bir Deneme

★☆☆ Gmail'de kendine gelen bir mail'i aç, "Orijinali göster" ile header'lara bak. Authentication-Results satırında SPF, DKIM ve DMARC sonuçları ne? header.d ile header.from hizalı mı?

★★☆ spf-lookups.sh'ı iyi bildiğin üç domain'de çalıştır. 10'a en yakın olan hangisi? Hangi include en çok sorgu harcıyor?

★★★ Laboratuvardaki DMARC kaydını p=reject; adkim=s; aspf=s yap ve aynı script'i tekrar çalıştır. Bookstore'un kendi DKIM'iyle imzalanan mail hâlâ geçiyor mu? Geçmiyorsa neden ve hangi iki çözümün var?

Bookstore'un web, API ve mail kayıtları artık yerinde. Ama bunları bir kez yazmak işin kolay kısmı; asıl zorluk değiştirmekte. Bir sonraki yazıda bir IP değişikliğini, PaaS'tan PaaS'a geçişi ve DNS sağlayıcısını taşımayı kesintisiz yapmak için TTL stratejisini, negative caching'i, DNSSEC'in (DNS cevaplarını imzalayan güvenlik uzantısı) klasik kırılma noktasını ve zone'u kod olarak yönetmeyi ele alıyoruz.