Nette Mail 4.1.3: aby tvé e-maily vůbec dorazily

před 2 hodinami od David Grudl  

Odeslat e-mail je jeden řádek kódu. Doručit ho je disciplína, ve které Gmail a Microsoft každým rokem přitvrzují pravidla. Nette Mail 4.1.3 přináší, co dnes provideři vyžadují: přihlášení přes OAuth, odhlášení jedním kliknutím i modernější DKIM. A jednu bezpečnostní záplatu, kvůli které se vyplatí aktualizovat hned.

Přitom jde o zpětně kompatibilní verzi: nic nepřepisuješ, jen aktualizuješ a nové možnosti si bereš podle potřeby. Projdeme je v pořadí, v jakém na tebe budou tlačit samy: od požadavků velkých providerů přes bezpečnost až po pohodlí při vývoji.

Heslo už nestačí: OAuth 2.0

Gmail i Microsoft 365 postupně vypínají přihlašování jménem a heslem pro SMTP; aplikační hesla jsou na odchodu a budoucnost patří OAuth tokenům. SmtpMailer proto umí autentizaci XOAUTH2:

$mailer = new Nette\Mail\SmtpMailer(
	host: 'smtp.gmail.com',
	username: 'franta@gmail.com',
	password: '',
	encryption: 'tls',
);
$mailer->setAccessToken($accessToken);

Access tokeny expirují, obvykle po hodině. Místo řetězce proto můžeš předat callback, který se zavolá při každém připojení a vždy dodá čerstvý token:

$mailer->setAccessToken(fn() => $oauth->getFreshToken());

Získání a obnovování tokenu zůstává na tvé OAuth knihovně, to je její řemeslo. Nette Mail řeší jen SMTP část, a tu kompletně: včetně správného ukončení výměny, když server token odmítne, takže v chybě uvidíš skutečný důvod, a ne záhadný timeout.

Odhlášení jedním kliknutím

Gmail a Yahoo vyžadují od hromadných odesílatelů, aby šel newsletter odhlásit jedním kliknutím přímo v klientovi. Technicky to znamená dvojici hlaviček podle RFC 8058, které musí přesně souhlasit; samotná hlavička List-Unsubscribe požadavek nesplňuje. Přesně tenhle typ znalosti má držet knihovna, ne tvůj kód:

$mail->setUnsubscribe('https://example.com/unsubscribe?token=xyz', 'unsubscribe@example.com');

První parametr je URL, které příjemce odhlásí v reakci na holý HTTP POST, druhý je e-mailová adresa jako záloha pro klienty, kteří POST odeslat neumějí. Obě hlavičky se nově také podepisují DKIM, takže adresu, na kterou kliknutí míří, nemůže nikdo cestou vyměnit.

DKIM: Ed25519 a jedna záplata

Podepisování e-mailů pomocí DKIM umí Nette Mail dávno. Nově zvládne kromě RSA i podpisy Ed25519 podle RFC 8463: klíč prostě předáš a knihovna sama pozná, o který typ jde: RSA je v PEM formátu, Ed25519 jsou surové bajty v base64. Žádný nový parametr, žádná konfigurace, jen potřebuješ rozšíření sodium.

Přibyl i takzvaný oversigning: v parametru oversignHeaders vyjmenuješ hlavičky, které se mají chránit proti připojení druhé kopie k už podepsané zprávě. To je oblíbený trik podvržených e-mailů, protože mnoho klientů zobrazí právě tu přidanou hlavičku From. S oversigningem takový pokus podpis rozbije.

A ta slíbená záplata: podpis už neobsahuje tag l= s délkou těla. Zněl neškodně, jenže umožňoval komukoli přilepit za podepsanou zprávu libovolný obsah, aniž by podpis přestal platit. To je samo o sobě důvod aktualizovat, i kdyby tě žádná z novinek nezajímala.

CssInliner se naučil kaskádu

Inlinování CSS do atributů style je u e-mailů nutnost, protože klienti značku <style> často ignorují. Dosud ale platilo jednoduché pravidlo: poslední deklarace vyhrává. Vezmi si tenhle stylesheet:

p.intro { color: red; }
p { color: blue; }

Prohlížeč vykreslí odstavec s třídou intro červeně, protože specifičtější selektor vítězí. Inliner ho ale obarvil modře a e-mail se tak lišil od webu, pro který byl stylesheet napsaný. Čím pečlivěji byly styly napsané, tím víc se výsledek rozcházel.

Od verze 4.1.3 řeší CssInliner konflikty skutečnou CSS kaskádou jako prohlížeč: !important porazí obyčejné deklarace, existující inline styl porazí selektory, pak rozhoduje specificita a teprve nakonec pořadí. Do výstupu se navíc zapíše jen vítězná hodnota, takže atributy style už nenafukují poražené deklarace. A oprava se dočkala i konverze na HTML atributy pro Outlook: width: auto dřív vygenerovalo width="0" a sloupec v Outlooku zkolaboval do nuly; teď se atribut prostě nevytvoří.

FileMailer a další drobnosti

Nový FileMailer nic neodesílá: každou zprávu zapíše jako soubor .eml do zadaného adresáře. Soubor otevřeš v libovolném e-mailovém klientovi a přesně vidíš, co by odešlo. Ideální do testů a při vývoji:

$mailer = new Nette\Mail\FileMailer(__DIR__ . '/mails');
$mailer->send($mail);

Kolem toho se sešla řada menších vylepšení, která poznáš, až o ně půjde:

  • Adresy s mezinárodní doménou jako jan@příklad.cz se automaticky převedou na punycode, takže je běžné SMTP servery neodmítnou.
  • STARTTLS má výchozí port 587 a vyjednává výhradně TLS 1.2 a 1.3; při selhání handshake se dozvíš skutečný důvod.
  • Persistentní spojení, které server mezitím zavřel, se před dalším odesláním tiše obnoví, místo aby každý pokus skončil chybou.
  • Velké přílohy se už nemohou tiše uříznout, když se zaplní odesílací buffer, a timeouty platí i proti serveru, který nikdy nepřestane odpovídat.
  • FallbackMailer se nevrací k mailerům, jejichž selhání je trvalé, například kvůli odmítnutým přihlašovacím údajům.

Jak aktualizovat

Verze 4.1.3 je zpětně kompatibilní, takže stačí obvyklé:

composer update nette/mail

Odeslat e-mail je pořád jeden řádek kódu. O to, aby skutečně dorazil, se teď o poznání víc postará knihovna. Kompletní přehled všech možností najdeš v dokumentaci.

David Grudl Tvůrce open-source projektů a specialista na AI, který lidem otevírá dveře do světa umělé inteligence. Jeho projekty Nette a další používají weby, které denně navštěvujete. Píše na Uměligence, La Trine a moderuje Tech Guys. Organizuje AI workshopy a věří, že technologie mají smysl jen tehdy, když lidem skutečně pomohou.