IlmHamroh
JavaScript Full-stack/29-qism. Backend integratsiyalar: fayl, to'lov, bot, realtime15/69-dars18 daqiqa
Mundarija (26)

Email deliverability: SPF, DKIM, DMARC va xat nega spam'ga tushadi

Qisqacha: Xat yuborildi — bu hali qutiga tushdi degani emas. Qabul qiluvchi server uch narsani tekshiradi: SPF (yuborgan IP domen ro'yxatidami), DKIM (xat yo'lda o'zgarmaganini isbotlovchi imzo) va DMARC (From: domeni shu tekshiruvlardan biriga mos va o'tganmi, o'tmasa nima qilish). Uchalasi domenning DNS'idagi TXT yozuvlar. Ulardan tashqari obro' muhim: bounce va shikoyatlarni kuzating, marketing xatlarda bir bosishda obunadan chiqish (List-Unsubscribe one-click) bering.

Bu darsda

  • SPF, DKIM va DMARC nimani tekshirishini va DNS'da qanday yozilishini tushuntirasiz.
  • Kanon tajribasidagi sakkiz holatni (forward, o'zgartirilgan xat, soxta yuboruvchi) o'qiy olasiz.
  • SPF tekshiruvi va DMARC moslik (alignment) qoidasini o'zingiz yozasiz.
  • One-click obunadan chiqish endpoint'ini yozasiz.
  • Bounce, shikoyat va provayderlar haqida qaror qabul qila olasiz.

Oldin bilishingiz kerak: Domen va DNS, crypto: hash, shifrlash va imzo (RSA imzo), Email yuborish: SMTP va Nodemailer, Email shablonlarini serverda render qilish.

Oldingi ikki darsda xatni yig'dik va yubordik. Mailpit'da hammasi chiroyli edi. Haqiqiy dunyoda esa Aziz shikoyat qiladi: "Tasdiqlash xati kelmadi". Xat Spam papkasida yotibdi.

1. Nega bu kerak?

Email 1980-yillarda ishonch asosida loyihalangan: SMTP'da From: ga istalgan manzilni yozish mumkin. Buni o'tgan darsdagi SMTP dialogida ko'rdingiz — MAIL FROM ni hech kim tekshirmadi. Firibgar From: bank@kapitalbank.uz deb yozib, "kartangiz bloklandi" xatini yuboradi.

Qabul qiluvchi serverlar (Gmail, Outlook, Mail.ru) bu bilan kurashadi: har xatni tekshiradi va baho beradi. Baho past bo'lsa — spam yoki umuman rad. Sizning to'g'ri xatingiz ham shu tekshiruvdan o'tadi.

O'xshatish: muhim hujjat notarius orqali yuboriladi. SPF — "bu xatni shu pochta bo'limidan yuborish mumkin" degan ro'yxat. DKIM — hujjatdagi muhr: yo'lda kimdir bir harfni o'zgartirsa, muhr buziladi. DMARC — "muhr yoki bo'lim tekshiruvdan o'tmasa, hujjatni yoqib yuboring" degan ko'rsatma. Uchalasini domen egasi e'lon qiladi, qabul qiluvchi faqat o'qiydi.

2. Uch yozuv

2.1 DNS'dagi TXT yozuvlar

Kanon vazifalar.example.uz domeni uchun to'rtta yozuv tuzdi (example — o'quv domeni, IP'lar — hujjatlar uchun ajratilgan RFC 5737 diapazonlari):

text
DNS yozuvlari (domen panelida qo'yiladi):
  s1._domainkey.vazifalar.example.uz   TXT  v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvSMIRNP3RKu6UA8pk/VHdLaeLysZ…
  vazifalar.example.uz                 TXT  v=spf1 ip4:192.0.2.10 include:_spf.provayder.example -all
  _spf.provayder.example               TXT  v=spf1 ip4:198.51.100.0/24 -all
  _dmarc.vazifalar.example.uz          TXT  v=DMARC1; p=reject; rua=mailto:dmarc@vazifalar.example.uz; adkim=s; aspf=r; pct=100
Yozuv Joyi Ma'nosi
SPF domenning o'zi 192.0.2.10 va provayder tarmog'i yubora oladi, qolganlar — yo'q (-all)
DKIM s1._domainkey.<domen> s1 selektorli ochiq kalit (RSA 2048)
DMARC _dmarc.<domen> o'tmasa — rad (p=reject), hisobotlar rua ga

Haqiqiy domen va haqiqiy DNS kanonda sinalmagan. DNS javoblari tajriba skriptidagi jadvaldan berildi. Lekin imzolash (Nodemailer) va tekshiruv (mailauth 8.0.1 — haqiqiy pochta serverlari ishlatadigan kod) — haqiqiy.

2.2 SPF: yuborgan IP ro'yxatdami

SPF (Sender Policy Framework) — domen nomidan xat yuborishga ruxsat berilgan IP'lar ro'yxati. Qabul qiluvchi server ulanayotgan IP'ni oladi, konvertdagi MAIL FROM domenining SPF yozuvini o'qiydi va solishtiradi. Mexanizmlar chapdan o'ngga tekshiriladi, birinchi mos kelgani hal qiladi:

  • ip4:192.0.2.10 yoki ip4:198.51.100.0/24 — bitta IP yoki tarmoq (IP manzil va port).
  • include:boshqa.domen — o'sha domenning SPF'ini ham tekshir (provayderingiz ro'yxati).
  • -all — qolgan hammasi fail; ~all — softfail (shubhali, lekin rad emas).

Asosiy qismni o'zimiz yozamiz:

js
const NATIJA = {
  "+": "pass", "-": "fail", "~": "softfail", "?": "neutral",
};
const songa = (ip) =>
  ip.split(".").reduce((a, q) => a * 256 + Number(q), 0);

function ichidami(ip, cidr) {
  const [tarmoq, uz = "32"] = cidr.split("/");
  const bloklar = 2 ** (32 - Number(uz));
  const bosh = Math.floor(songa(tarmoq) / bloklar) * bloklar;
  const x = songa(ip);
  return x >= bosh && x < bosh + bloklar;
}

function spfBahola(yozuv, ip) {
  for (const q of yozuv.split(" ").slice(1)) {
    const belgi = "+-~?".includes(q[0]) ? q[0] : "+";
    const mexanizm = belgi === q[0] ? q.slice(1) : q;
    const ip4 = mexanizm.startsWith("ip4:")
      && ichidami(ip, mexanizm.slice(4));
    const mos = mexanizm === "all" || ip4;
    if (mos) return NATIJA[belgi];
  }
  return "neutral";
}

const spf = "v=spf1 ip4:192.0.2.10 ip4:198.51.100.0/24 -all";
console.log(ichidami("198.51.100.25", "198.51.100.0/24"));
console.log(spfBahola(spf, "192.0.2.10"));
console.log(spfBahola(spf, "198.51.100.25"));
console.log(spfBahola(spf, "203.0.113.9"));
console.log(spfBahola("v=spf1 ip4:192.0.2.10 ~all", "203.0.113.9"));

Konsolda:

text
true
pass
pass
fail
softfail
  • songa — IP'ni bitta songa aylantiradi (to'rt bayt, 256 lik sanoq).
  • /24 — oxirgi 8 bit erkin: 256 manzil. bosh — tarmoqning birinchi manzili.
  • Prefiks bo'lmasa (ip4:192.0.2.10), mexanizm + hisoblanadi.

SPF'ning zaif joyi: u konvert domenini tekshiradi, Aziz ko'radigan From: ni emas. Firibgar konvertga o'z domenini, From: ga esa bankni yozsa — SPF uning domeni bo'yicha pass beradi. Bu teshikni DMARC yopadi.

2.3 DKIM: xatdagi muhr

DKIM (DomainKeys Identified Mail) — xatning sarlavhalari va tanasi ustidan raqamli imzo. Yuboruvchi server yopiq kalit bilan imzolaydi va xatga DKIM-Signature sarlavhasini qo'shadi. Qabul qiluvchi d= (domen) va s= (selektor) bo'yicha DNS'dan ochiq kalitni olib, imzoni tekshiradi (crypto: imzo). Kanon imzosining boshi:

text
DKIM-Signature: DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;…

Nodemailer'da bu bitta sozlama — Pochta modulidagi dkim:

js
import { generateKeyPairSync } from "node:crypto";

// bir marta: kalit juftligi; yopiq — .env ga, ochiq — DNS ga
const { privateKey, publicKey } = generateKeyPairSync("rsa", {
  modulusLength: 2048,
});
const dnsQiymat = publicKey
  .export({ type: "spki", format: "der" })
  .toString("base64"); // TXT: v=DKIM1; k=rsa; p=<dnsQiymat>

const transporter = nodemailer.createTransport({
  host, port, secure, auth,
  dkim: {
    domainName: "vazifalar.example.uz",
    keySelector: "s1",
    privateKey: process.env.DKIM_YOPIQ_KALIT,
  },
});

Amalda provayder (SES, Postmark, Resend) DKIM'ni o'zi qo'yadi: u sizga 2–3 ta DNS yozuvini beradi, kalit esa uning serverida. O'zingiz imzolash — o'z SMTP serveringiz bo'lsa kerak. Selektor kalitni almashtirishga yordam beradi: yangi kalit s2 ga qo'yiladi, eski s1 bir muddat qoladi, keyin o'chiriladi.

2.4 DMARC: From: bilan moslik

DMARC (Domain-based Message Authentication, Reporting and Conformance) ikki ish qiladi:

  1. Moslik (alignment): SPF yoki DKIM pass bo'lishi yetmaydi. Tekshirilgan domen Aziz ko'radigan From: domeni bilan mos bo'lishi kerak. aspf=r — SPF uchun yumshoq (relaxed) moslik: bir tashkilot domeni yetarli (bounce.example.uz va vazifalar.example.uz). adkim=s — DKIM uchun qat'iy (strict): domenlar aynan bir xil.
  2. Siyosat: ikkalasi ham o'tmasa nima qilish — p=none (faqat hisobot), p=quarantine (spam), p=reject (rad).

rua — qabul qiluvchilar har kuni yuboradigan yig'ma hisobotlar manzili: kim sizning domeningizdan xat yubordi va natijasi qanday.

flowchart TD
  X[Xat keldi] --> S{SPF pass va From ga mos?}
  S -->|ha| P[DMARC pass]
  S -->|yo'q| D{DKIM pass va From ga mos?}
  D -->|ha| P
  D -->|yo'q| F[DMARC fail]
  F --> Q{p= siyosati}
  Q -->|none| N[qabul, hisobot]
  Q -->|quarantine| SP[Spam]
  Q -->|reject| R[Rad etish]

Diagrammaga qarang: DMARC uchun bittasi yetarli — SPF yoki DKIM. Bu keyingi bo'limdagi forward holatini tushuntiradi.

3. Kanon tajribasi: sakkiz holat

3.1 Natijalar

Kanon bitta imzolangan xatni turli sharoitda tekshirdi. Har qatorning oxirgi ustuniga — DMARC natijasiga qarang: xatning taqdirini aynan u hal qiladi:

text
to'g'ri: DKIM + SPF (o'z IP)                 DKIM pass | SPF pass | DMARC pass (p=reject)
provayder IP'si (include:)                   DKIM pass | SPF pass | DMARC pass (p=reject)
begona IP, DKIM bor (forward kabi)           DKIM pass | SPF fail | DMARC pass (p=reject)
imzosiz, begona IP (soxta yuboruvchi)        DKIM none (message not signed) | SPF fail | DMARC fail (p=reject)
imzodan keyin mavzu o'zgartirildi            DKIM fail (bad signature) | SPF pass | DMARC pass (p=reject)
tana o'zgartirildi (ro'yxat footer qo'shdi)  DKIM neutral (body hash did not verify) | SPF pass | DMARC pass (p=reject)
boshqa kalit bilan imzolangan                DKIM fail (bad signature) | SPF pass | DMARC pass (p=reject)
From: boshqa domen, DKIM bizniki (aligned emas) DKIM pass | SPF pass | DMARC none

Qatorma-qator:

  1. To'g'ri — hammasi pass.
  2. Provayder IP'si — include: ishladi.
  3. Forward — Aziz xatlarini boshqa qutiga yo'naltirgan. Endi xatni boshqa server, boshqa IP'dan uzatadi: SPF fail. Lekin DKIM imzosi xat bilan birga keldi — pass. DMARC'ga bittasi yetarli — pass. DKIM — forward'dan omon qoladigan yagona tekshiruv.
  4. Soxta yuboruvchi — imzo yo'q, IP begona: DMARC fail, p=reject — xat rad etiladi. Firibgar to'xtadi.
  5. Mavzu o'zgartirildi — "bad signature": imzolangan sarlavha o'zgardi. SPF hali pass bo'lgani uchun DMARC o'tdi.
  6. Tana o'zgartirildi — "body hash did not verify". Pochta ro'yxatlari (mailing list) xat oxiriga futer qo'shganda shunday bo'ladi.
  7. Begona kalit — imzo bor, lekin DNS'dagi ochiq kalitga mos emas.
  8. From: boshqa domen — DKIM va SPF biznikida pass, lekin From: bank@bank.example. DMARC bank.example ning yozuvini qidirdi — yo'q, natija none. Agar bankning DMARC'i p=reject bo'lganda, xat rad etilardi: imzo bizniki, From: esa bankniki — moslik yo'q.

5- va 6-qatorlarga yana qarang: DKIM buzilgan, lekin xat SPF tufayli o'tdi. Xat forward qilinib, yo'lda o'zgartirilsa — ikkalasi ham yiqiladi. Shuning uchun ikkalasini sozlang: biri tushsa, ikkinchisi ushlaydi.

Tekshirib ko'ring: Kompaniya DMARC'ni darhol p=reject bilan yoqdi. Ertasiga buxgalteriya xatlari mijozlarga yetmay qoldi. Nima bo'lgan bo'lishi mumkin?

Javob

Buxgalteriya xatlarni boshqa xizmat (masalan, hisob-faktura dasturi) orqali yuborar edi. Uning IP'si SPF'da yo'q, DKIM imzosi kompaniya domeni bilan emas. DMARC fail, p=reject — rad. Shuning uchun DMARC bosqichma-bosqich yoqiladi: avval p=none va rua hisobotlari — kim domeningizdan yuborayotganini ko'rasiz, hammasini SPF/DKIM'ga qo'shasiz, keyin quarantine, oxirida reject.

3.2 rDNS va HELO

Yana ikki tekshiruv o'z serverdan yuborganda muhim (kanonda sinalmagan, hujjat bo'yicha):

  • rDNS (teskari DNS, PTR yozuvi): 192.0.2.10 IP'si qaysi nomga tegishli. U mail.vazifalar.example.uz ga, o'sha nom esa yana shu IP'ga ko'rsatsin. PTR'ni domen paneli emas, IP egasi (hosting) qo'yadi.
  • HELO/EHLO nomi — server o'zini tanishtirgan nom; u ham haqiqiy, DNS'da bor nom bo'lsin.

Provayder orqali yuborganda bu ikkalasi provayderning ishi.

4. Obro', bounce va shikoyatlar

4.1 Tranzaksion va marketing xatlar

Autentifikatsiya — eshikdan kirish. Qutiga tushish esa obro'ga (reputation) bog'liq: domeningiz va IP'ingizdan kelgan xatlar qanchalik ko'p ochiladi, spam deb belgilanadi, mavjud bo'lmagan manzillarga ketadi.

Ikki turdagi xatni ajrating:

Tranzaksion Marketing
Misol tasdiqlash, parol, eslatma yangiliklar, aksiya
Kim boshlaydi foydalanuvchi harakati siz
Obunadan chiqish shart emas (tasdiqlash xatida) majburiy, bir bosishda
Yuborish yo'li alohida oqim yoki subdomen alohida oqim yoki subdomen

Ularni ajratishning sababi: marketing xati spam deb belgilansa, uning obro'si tushadi. Bitta domen va IP bo'lsa, parol tiklash xati ham spam'ga tusha boshlaydi. Provayderlar buni "stream" yoki alohida subdomen (news.vazifalar.example.uz) bilan hal qiladi.

4.2 Google va Yahoo talablari

2024-yil 1-fevraldan Gmail va Yahoo ommaviy yuboruvchilardan (bulk sender) qo'shimcha talab qiladi. Google qo'llanmasida bu — Gmail manzillariga kuniga 5 000 dan ortiq xat yuboradigan. Talablar (ikkala hujjat, 2026-10-28 holati):

Talab Google Yahoo
SPF va DKIM ikkalasi ham ikkalasi ham
DMARC kamida p=none kamida p=none, DMARC pass bo'lsin
One-click obunadan chiqish (RFC 8058) marketing va obuna xatlarida marketing va obuna xatlarida
Spam shikoyatlari ulushi 0.10 % dan past tuting, hech qachon 0.30 % ga yetmasin 0.3 % dan past

DMARC'ni p=none bilan boshlashga ruxsat bor — bu «Tekshirib ko'ring» savolidagi bosqichma-bosqich yo'lga mos. Talablar o'zgarishi mumkin — joriy holatni manbalardagi havoladan tekshiring.

4.3 One-click obunadan chiqish

Pochta.yubor() obunaBekor berilganda ikki sarlavha qo'shadi. Biz modulni lokal SMTP serverga ulab, xom xatni ko'rdik (nodemailer 10.1.7):

text
List-Unsubscribe-Post: List-Unsubscribe=One-Click
List-Unsubscribe: <https://vazifalar.example.uz/obuna/x1> (Obunani bekor
 qilish)

List-Unsubscribe (RFC 2369) — obunadan chiqish manzili; Gmail uni xat tepasida "Unsubscribe" tugmasi qilib ko'rsatadi. Ikkinchi qatordagi bo'shliq bilan boshlangan davom — sarlavhani bo'lish (folding): uzun sarlavha bir necha qatorga yoyiladi. List-Unsubscribe-Post (RFC 8058) — "bu havolaga POST yuborilsa, hech qanday sahifasiz obuna bekor bo'ladi".

Server tomoni — bitta POST marshruti. Toza node:http bilan:

js
import { createServer } from "node:http";

const obunalar = new Map([
  ["x1", { email: "aziz@example.uz", faol: true }],
]);

const server = createServer(async (req, res) => {
  const m = req.url.match(/^\/obuna\/(\w+)$/);
  const obuna = m && obunalar.get(m[1]);
  if (req.method !== "POST" || !obuna) {
    res.writeHead(404).end();
    return;
  }
  let tana = "";
  for await (const b of req) tana += b;
  if (tana !== "List-Unsubscribe=One-Click") {
    res.writeHead(400).end();
    return;
  }
  obuna.faol = false;
  res.writeHead(200).end("bekor qilindi");
});
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const url = `http://127.0.0.1:${server.address().port}/obuna/x1`;

const r1 = await fetch(url, {
  method: "POST",
  headers: { "content-type": "application/x-www-form-urlencoded" },
  body: "List-Unsubscribe=One-Click",
});
console.log(r1.status, await r1.text(), obunalar.get("x1"));
const r2 = await fetch(url);
console.log("GET:", r2.status, obunalar.get("x1").faol);
server.closeAllConnections();
server.close();

Konsolda:

text
200 bekor qilindi { email: 'aziz@example.uz', faol: false }
GET: 404 false

Nega GET ga javob yo'q? Antivirus va korporativ pochta filtrlari xatdagi havolalarni oldindan ochib tekshiradi. GET obunani bekor qilsa, foydalanuvchi hech narsa bosmasdan obunadan chiqib qoladi. RFC 8058 aynan shuning uchun POST talab qiladi. x1 — taxmin qilib bo'lmaydigan token bo'lsin: ketma-ket ID (/obuna/42) bilan hujumchi hammaning obunasini bekor qiladi. Prodda obunalar — baza, token esa Bildirishnoma tizimi darsidagi foydalanuvchi afzalliklariga bog'lanadi.

4.4 Bounce va shikoyat

  • Hard bounce — doimiy rad (550, "bunday quti yo'q"). Manzil darhol bostirish ro'yxatiga (suppression list) tushadi — unga boshqa yuborilmaydi.
  • Soft bounce — vaqtinchalik (4xx, quti to'la). Bir necha marta qayta urinib, keyin to'xtatiladi.
  • Shikoyat (complaint) — foydalanuvchi "Spam" tugmasini bosdi. Darhol bostirish ro'yxatiga.

Provayderlar bu hodisalarni sizga webhook bilan yuboradi — o'z serveringizdagi URL'ga HTTP POST (Kiruvchi webhook'larni qabul qilish darsida chuqur). Amazon SES — SNS orqali, Postmark va Resend — to'g'ridan. Bu webhook'lar kanonda sinalmagan (provayder hisobi kerak) — formatlari hujjatlarda.

O'lik manzillarga yuborishda davom etish — obro'ni eng tez tushiradigan xato. Ro'yxatdan o'tishda emailni tasdiqlash (28-qismda) — ham xavfsizlik, ham obro' uchun.

5. Provayder tanlash

Kanon hech bir provayderni sinamagan. Tanlashda qaraladigan narsalar:

Mezon Nimaga qarash
Autentifikatsiya DKIM, SPF (include), custom return-path
Webhook bounce, complaint, delivery hodisalari
Oqimlar tranzaksion va marketing ajratilganmi
Narx va limit bepul daraja, soniyadagi xat

Keng tarqalganlari: Amazon SES (arzon, sozlash ko'proq) va Postmark (tranzaksion xatlarga ixtisoslashgan). Yana Resend (dasturchilar uchun, React Email bilan bir jamoa) va Brevo (marketing va tranzaksion). Pochta moduli SMTP orqali ishlagani uchun ulardan istalganiga faqat .env o'zgaradi.

6. Hujumchi nigohi

Hujum Himoya
sizning domeningizdan soxta xat (spoofing) SPF + DKIM + DMARC p=reject — kanonda 4-holat rad
o'xshash domen (vazifa1ar.uz) DMARC yordam bermaydi; foydalanuvchini o'rgatish, brend monitoringi
DKIM yopiq kaliti sizdi kalit .env da; yangi selektor bilan almashtirish
ketma-ket obuna tokeni tasodifiy token
GET bilan obunadan chiqarish faqat POST (RFC 8058)

Birinchi qator — DMARC'siz domen firibgarlar uchun ochiq eshik. Hatto domen umuman xat yubormasa ham, unga v=spf1 -all va p=reject qo'yish tavsiya etiladi — aks holda uning nomidan bemalol yuborishadi.

7. Ko'p uchraydigan xatolar

Xato Natija To'g'risi
ikkita SPF yozuvi SPF permerror bitta yozuv, ichida bir nechta mexanizm
include juda ko'p 10 ta DNS so'rov chegarasi keraksizlarini olib tashlash
darhol p=reject o'z xatlaringiz rad none → quarantine → reject
faqat SPF forward'da yiqiladi DKIM ham
From: va DKIM domeni boshqa DMARC moslik yo'q d= = From: domeni
bounce'larni e'tiborsiz qoldirish obro' tushadi bostirish ro'yxati
marketing va tranzaksion bitta oqimda parol xati spam'ga alohida oqim/subdomen

8. Mashqlar

Mashqlar kurs/mashqlar/29/15-deliverability/ da.

1-mashq (oson): qaysi tekshiruv?

Har holat uchun qaysi tekshiruv yiqilishini yozing (SPF, DKIM yoki hech biri):

  1. Xat forward qilindi, mazmuni o'zgarmadi.
  2. Pochta ro'yxati xat oxiriga reklama qo'shdi, o'z serveridan uzatdi.
  3. Provayder IP'si include: da bor.
  4. Firibgar o'z serveridan imzosiz yubordi.
Yechim
  1. SPF — IP o'zgardi; DKIM omon qoldi (kanon: DMARC pass).
  2. SPF va DKIM — IP ham, tana ham o'zgardi; DMARC fail. Shuning uchun pochta ro'yxatlari ARC (Authenticated Received Chain — "xat menga kelganda tekshiruvlardan o'tgan edi" degan oraliq server imzosi) kabi qo'shimcha mexanizmlar ishlatadi.
  3. Hech biri — include provayder tarmog'ini qamraydi.
  4. SPF va DKIM — kanon: DKIM none, SPF fail, DMARC fail → rad.

2-mashq (o'rta): DMARC moslik

mosmi(fromDomen, tekshirilganDomen, rejim) funksiyasini yozing: rejim === "s" — domenlar aynan bir xil (katta-kichik harf farqsiz); "r" — tashkilot domeni bir xil (soddalashtirilgan: oxirgi ikki qism, vazifalar.example.uz → example.uz). Ishora: split("."), slice(-2), join(".").

Yechim
js
const tashkilot = (domen) =>
  domen.toLowerCase().split(".").slice(-2).join(".");

function mosmi(fromDomen, tekshirilgan, rejim) {
  if (rejim === "s") {
    return fromDomen.toLowerCase() === tekshirilgan.toLowerCase();
  }
  return tashkilot(fromDomen) === tashkilot(tekshirilgan);
}

const FROM = "vazifalar.example.uz";
console.log(mosmi(FROM, "vazifalar.example.uz", "s"));
console.log(mosmi(FROM, "bounce.example.uz", "s"));
console.log(mosmi(FROM, "bounce.example.uz", "r"));
console.log(mosmi(FROM, "Vazifalar.Example.UZ", "s"));
console.log(mosmi(FROM, "provayder.example", "r"));

Konsolda:

text
true
false
true
true
false

Uchinchi qator — provayderlar aynan shunga tayanadi: konvert manzili bounce.example.uz, From: esa vazifalar.example.uz. Yumshoq rejimda SPF mos. Oxirgi qator: provayder domenidagi SPF pass bizning From: ga yordam bermaydi. "Oxirgi ikki qism" — soddalashtirish: co.uk kabi domenlarda u xato. Haqiqiy kutubxonalar Public Suffix List ro'yxatidan foydalanadi.

3-mashq (qiyin): include va 10 so'rov chegarasi

Darsdagi spfBahola ni spf(domen, ip) ga aylantiring: yozuv DNS obyektidan olinadi (yo'q bo'lsa — "none"), include: boshqa domenni rekursiv tekshiradi (ichki natija pass bo'lsa — mos). Har include hisoblanadi, 10 dan oshsa — "permerror". O'zini o'zi include qiladigan yozuv bilan sinang. Ishora: hisoblagichni obyekt sifatida uzating — rekursiv chaqiruvlar uni bo'lishadi (Rekursiya asoslari).

Yechim
js
const DNS = {
  "vazifalar.example.uz":
    "v=spf1 ip4:192.0.2.10 include:_spf.prov.example -all",
  "_spf.prov.example": "v=spf1 ip4:198.51.100.0/24 -all",
  "halqa.example": "v=spf1 include:halqa.example -all",
};
const NATIJA = {
  "+": "pass", "-": "fail", "~": "softfail", "?": "neutral",
};
const songa = (ip) =>
  ip.split(".").reduce((a, q) => a * 256 + Number(q), 0);

function ichidami(ip, cidr) {
  const [tarmoq, uz = "32"] = cidr.split("/");
  const bloklar = 2 ** (32 - Number(uz));
  const bosh = Math.floor(songa(tarmoq) / bloklar) * bloklar;
  return songa(ip) >= bosh && songa(ip) < bosh + bloklar;
}

function spf(domen, ip, hisob = { soni: 0 }) {
  const yozuv = DNS[domen];
  if (!yozuv) return "none";
  for (const q of yozuv.split(" ").slice(1)) {
    const belgi = "+-~?".includes(q[0]) ? q[0] : "+";
    const mex = belgi === q[0] ? q.slice(1) : q;
    let mos = mex === "all";
    if (mex.startsWith("ip4:")) mos = ichidami(ip, mex.slice(4));
    if (mex.startsWith("include:")) {
      hisob.soni++;
      if (hisob.soni > 10) return "permerror";
      const ichki = spf(mex.slice(8), ip, hisob);
      if (ichki === "permerror") return ichki;
      mos = ichki === "pass";
    }
    if (mos) return NATIJA[belgi];
  }
  return "neutral";
}

console.log(spf("vazifalar.example.uz", "192.0.2.10"));
console.log(spf("vazifalar.example.uz", "198.51.100.25"));
console.log(spf("vazifalar.example.uz", "203.0.113.9"));
console.log(spf("yoq.example", "192.0.2.10"));
console.log(spf("halqa.example", "192.0.2.10"));

Konsolda:

text
pass
pass
fail
none
permerror

halqa.example o'zini o'zi chaqiradi — chegarasiz rekursiya stekni to'ldirardi (Rekursiya asoslari). Haqiqiy SPF standarti (RFC 7208) ham aynan 10 ta DNS so'rov chegarasini qo'yadi — hujumchi bitta xat bilan qabul qiluvchi serverni minglab DNS so'roviga majburlamasin. Shu sababli bir nechta provayderni include qilgan kompaniyalar bu chegaraga tez-tez uriladi.

9. Loyiha qadami: tajriba, modulga yangi kod yo'q

Bu dars Pochta moduliga yangi kod qo'shmaydi — kerakli ikki imkoniyat avvaldan bor:

  • dkim sozlamasi — o'z SMTP serveri bilan imzolash uchun; provayder orqali yuborilsa, berilmaydi.
  • obunaBekor → List-Unsubscribe va List-Unsubscribe-Post. Kanon testi (Mailpit orqali) sarlavhalarni aynan tekshiradi (qatorlarni telefon uchun bo'ldik):
ts
const h = await sarlavhalar(x.ID);
assert.deepEqual(h["List-Unsubscribe"],
  ["<https://v.example/obuna/xyz> (Obunani bekor qilish)"]);
assert.deepEqual(h["List-Unsubscribe-Post"],
  ["List-Unsubscribe=One-Click"]);

SPF, DKIM, DMARC tekshiruvi esa domen va DNS bilan bog'liq — u kodda emas, domen panelida. Kanon uni tajribada (mailauth bilan) ko'rsatdi va loyihaga kiritmadi: haqiqiy domen va provayder yo'q.

vazifalar-api da one-click marshruti 28-qism tugagach qo'shiladi: u foydalanuvchining email bildirishnomalari afzalligini o'chiradi.

10. Real ishda

  • Deploy ro'yxati: yangi domen ochilganda SPF, DKIM (provayder bergan yozuvlar) va DMARC p=none + rua — birinchi kunning ishi. Hisobotlarni o'qish uchun DMARC tahlil xizmatlari bor.
  • Pochta testerlari (mail-tester, Gmail'dagi "Show original") xatning SPF, DKIM, DMARC natijalarini ko'rsatadi.
  • Kompaniyalarda DMARC'ni reject gacha olib borish — xavfsizlik jamoasining alohida loyihasi.
  • Intervyuda: "SPF, DKIM, DMARC farqi?", "Forward qilingan xat nega o'tadi?", "Xatlarimiz spam'ga tushyapti — qayerdan boshlaysiz?"

Xulosa

  • SPF — konvert domeni uchun ruxsat etilgan IP'lar; forward'da yiqiladi.
  • DKIM — sarlavha va tana imzosi; yo'lda o'zgarsa fail, forward'dan omon qoladi.
  • DMARC — From: ga mos SPF yoki DKIM pass; siyosat none → quarantine → reject.
  • Kanon: soxta yuboruvchi p=reject bilan rad; forward — DKIM tufayli o'tdi.
  • One-click obunadan chiqish — faqat POST va tasodifiy token.
  • Hard bounce va shikoyat — darhol bostirish ro'yxatiga.

Keyingi dars: SMS provayderlar: Eskiz va PlayMobile — O'zbekistonda SMS yuborish: token, segmentlar narxi va bitta ʻ belgisi xabarni qanday qimmatlashtirishi.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!