Potvrzení o přijetí mailu

vložil Radek Červinka 15. října 2013 22:37

Nedávno jsem programoval komunikační modul do našeho systému a jedna z jeho součástí byla funkce potvrzování doručení mailů a to na obou úrovních které znám.

Samozřejmě jsem použil jako vždy Synapsi a rád bych zde o tom napsal několik poznámek.

Oprava: v tom MailToDNS byla chybka - za tím rfc822 se má přidat emailová adresa. Opraveno. Sice to fungovalo i tak, ale přesto.

Potvrzování funguje na dvou stupních:

  • potvrzení o přečtení (resp. zobrazení mailovým klientem)
  • potvrzení o doručení cílovému serveru

První část je zajišťována vhodnou strukturou hlaviček zprávy, konkrétně přidáním Disposition-Notification-To.

var
  oMail:TMimeMess; 
….
// vytvoření zprávy
  oMail.Header.CustomHeaders.Add('Disposition-Notification-To:'+sReply)
//poslat via SMTP, např. funkcí SendToRaw z jednotky smtpsend

To vcelku nebyl problém.

Horší bylo implementovat druhý bod - potvrzení o doručení. Tato informace je zasílána přímo SMTP serverem a to v případě kdy:

  • to SMTP server umí (musí umět rozšíření ESMTP, což není vždy)
  • je o to při komunikaci požádán

Standardně Synapse podporuje podmnožinu příkazů z rozšíření ESMTP, přičemž tato část podporována není. Naštěstí kód synapse je přímý jak žebřík, tudíž pokud člověk ví co chce tak úpravu provede i kutil Tim. Výhodou Synapse je to, že se jedná o knihovnu tříd, takže si je člověk může začlenit do svého projektu a přiohnout jak potřebuje, přičemž pro jiný projekt může například použít zase "vanilla" verzi bez úprav.

Jak tedy na to?

Předpokládám, že máte sestrojen mail a používáte např. funkci SendToRaw. Začneme tím, že si tuto pomocnou funkci zkopírujeme a budeme ji rozšiřovat. Tato funkce v podstatě vytvoří instanci třídy TSMTPSend, která implementuje komunikaci se SMTP serverem. Odeslání mailu spočívá v podstatě z přihlášení k serveru, zaslání informací jako je adresát a odesílatel a poslání dat, následované logout.

Toto je samozřejmě jednoduchá varianta, která nám stačit nebude. Popsaný kód rozšíříme o test zda server podporuje ESMTP rozšíření a pokud ano, tak zda z plejády rozšíření podporuje DSN (Delivery Status Notifications).

Tzn. něco ve smyslu:

var
  SMTP: TSMTPSend;
…

    if SMTP.Login then
    begin
      bSupportDSN := SMTP.ESMTP and (SMTP.ESMTPcap.IndexOf('DSN') > -1);
// podporuje SMTP naše rozšíření?

Nyní SMTP server může dostat informaci o adresátovi, v Synapsi metoda MailTo

function TSMTPSend.MailTo(const Value: string): Boolean;
begin
  FSock.SendString('RCPT TO:<' + Value + '>' + CRLF);
  Result := ReadResult div 100 = 2;
end;

Jak jsem uvedl, je to velmi čitelný kód. Pokud chceme ale informaci o doručení je nutno toto změnit. Problémek je, že metoda ReadResult je private. Pod minulým článkem oxo uvedl, že na private položky se dá dostat přes class helper, ale v mém případě bylo jednodušší přidat další metodu.

function TSMTPSend.MailToDNS(const Value: string): Boolean;
begin
  FSock.SendString('RCPT TO:<' + Value + '> NOTIFY=FAILURE,SUCCESS ORCPT=rfc822;'+Value  + CRLF);
  Result := ReadResult div 100 = 2;
end;

Pokud porovnáte s předchozím kódem tak uvidíte, že prosím SMTP server aby mi dal vědět v případě jak selhání, tak úspěchu. V předchozím případě by pro většinu SMTP serverů byl nastaven ekvivalent NOTIFY=FAILURE, tj. poslat jen při chybě. ORCPT značí, že SMTP server by měl ponechat původní informace o hlavičkách.

No a nyní stačí jen čekat na odpověď od serveru, která bude poslána na zadaný mail. Ten obsahuje část s identifikací Content-Type: multipart/report a pak buďto report-type=delivery-status nebo report-type=disposition-notification. Mail stáhnete přes POP3 nebo IMAP a není třeba stahovat vše, ale jen hlavičky (aspoň v první fázi).

Berte tento text spíše jako nakopnutí než kompletní řešení. Ještě jednou díky Lukáši za Synapsi.


Nabízíme Delphi školení na různá témata, primárně ve Vaší firmě.

Tagy: ,

Praxe

Komentáře

16.10.2013 10:53:06 #

z.

Pripadne, pokud by chtel nekdo to same v Indy, tak upravu lze provest v IdSMTPBase.pas, procedura WriteRecipientNoPipelining ;)

z.

16.10.2013 16:45:11 #

geby

ja bych to cele jeste zjednodusil. Treba jsi prehledl FindCap metodu, tudiz jde napsat:

bSupportDSN := SMTP.FindCap('DSN') <> '';

Neni treba se vubec zabyvat tim, jestli server podporuje ESMTP, protoze v nulove mnozine ESMTP rozsireni se to urcite nenajde. ;-)

Taktez povazuji pridani dalsi metody jako spravny postup, zatimco modifikaci stavajici metody MailTo bych povazoval za nerozumne. To proto, ze ta nova metoda dela neco trochu jineho, a navic je potreba jen nekdy. (jen kdyz to druha strana vubec podporuje.)

A namet - kdyz uz se dela nova metoda s pozadavkem na zaslani DSN, nebylo by od veci k ni pridat parametr, kterym by se urcoval druh notifikace. (jestli success, nebo failure, nebo oboje.)

A v neposledni rade obecny apel - kdyz narazite na neco, co by potrebovalo v Synapsi priohnout, nebojte se mi napsat. Pokud to nebude nejaka uplna fikovina, muze se to brzy objevit ve snapshotu. A i kdyz ne, budu mit alespon prehled o tom, co kdo s tim pacha a mohu na to myslet pri dalsim vyvoji.

geby

18.10.2013 16:56:17 #

radekc

Máš geby pravdu, jinak je ještě třetí možnost DELAY. A navíc za tím rfc822 v té metodě chyběla v příkladu adresa, už je to opraveno - fungovalo to i tak.

radekc

19.10.2013 10:41:38 #

oxo

Pokud nepotřebuješ definovat další proměnné ve třídě, nebo jinak měnit kód, myslím, že toto je právě učebnicová ukázka použití class helperu:

unit smtpsenddns;

interface

uses blcksock, smtpsend;

type

  TSMTPSendHelper = class helper for TSMTPSend
  public
    function MailToDNS(const Value: string): Boolean;
  end;

implementation

{ TSMTPSendHelper }

function TSMTPSendHelper.MailToDNS(const Value: string): Boolean;
begin
  Self.FSock.SendString('RCPT TO:<' + Value + '> NOTIFY=FAILURE,SUCCESS ORCPT=rfc822;'+Value  + CRLF);
  Result := Self.ReadResult div 100 = 2;
end;

end.

Výhodou je samozřejmě odpadnutí nutnosti modifikovat původní kód.

oxo

20.10.2013 12:14:38 #

geby

Nejak ti nerozumim. Proc je to lepsi nez udelat si obycejneho potomka tridy?

Myslim si, ze je to naopak klasicka ukazka toho, kde by se classhelper pouzivat NEMEL!

Tady totiz nepotrebujes modifikovat puvodni kod, potrebujes naopak nejaky te kod pridat. Potrebujes pridat metodu, ale na stavajicim kdu tridy nepotrebujes zmenit ani carku! Zaroven chte-nechte musis modifikovat tvuj kod, ktery tridu vola. (jinak se ti ta pridana metoda sama jaksi nezavola, ze?) Takze proc neudelat potomka tridy, ktery doplni novou metodu? Zmenit inicializaci instance tridy na tvoji novou tridu je uz upravdu uplna drobnost, zadna prekazka.

geby

20.10.2013 15:37:56 #

oxo

"Proc je to lepsi nez udelat si obycejneho potomka tridy?"

Nevšiml jsem si, že bych někde psal, že to je lepší. Je to alternativa.

"Tady totiz nepotrebujes modifikovat puvodni kod, potrebujes naopak nejaky te kod pridat. Potrebujes pridat metodu, ale na stavajicim kdu tridy nepotrebujes zmenit ani carku!"

To podle mě není pravda, potomkem třídy se nedostanu na private metody a proměnné (FSock a ReadResult).
+ Sakra, stále jsem si myslel, že ClassHelper právě slouží k tomu, přidat nějaké třídě nové metody. Je to jinak?

---

Jak bych to asi řešil já: do nové unity bych si dal ten ClassHelper + vlastní modifikaci SendToRaw a kód mně bude fungovat bez nutnosti změnit původní knihovnu Synapse - a tudíž, když si Synapse náhodou po nějakém čase zaktualizuju, nebudu muset myslet na ty změny, které jsem v ní napáchal a všechno bude fungovat bez nutnosti opětovně zasahovat do kódu...

Ale tak byl to jen návrh, ne osobní výpad na někoho, popř. výzva k začátku flame :) Neznám formulku na ideální kód ani život, berte to jako brainstorming :)

oxo

20.10.2013 21:02:05 #

geby

Opravdu se potomkem tridy nedostanes k private metodam? Ono tohle uz neplati?

"In a protected section, Fields, Properties and Methods are declared to be accessible to this class and classes descending from it. But not accessible externally by class users."

(Krom toho, i kdyby to neplatilo, tak treba u FSock problem nemas, to je pristupne verejne pres property Sock.)

Tak nejak zivot pisatele knihoven mne naucil, ze nove vymozenosti jazyka se maji pouzivat jen tehdy, kdyz je to opravdu nutne, ci kdyz to prinese vyraznou usporu prace. Ale pouzit to misto klasicke dedicnosti s tim, ze je ve finale stejne pracne, nepovazuji za moudre.

A neboj, beru to take jako ciste akademickou debatu.

geby

20.10.2013 21:29:24 #

geby

Teda, co to tam melu? mel sjem na mysli protected metodam, protoze k FSock se dostat nepotrebujes, to ma public property, a Readresult je protected, ne private. Tak jsem to myslel.

geby

21.10.2013 0:42:15 #

oxo

A jo, to jsem přehlédl, že Sock je také v public. Ale ReadResult mám v private, asi stará verze.

Každopádně díky za Synapse, bez ní bych byl nahranej! :) Všechna komunikace mi běží přes ní!

oxo

21.10.2013 14:16:35 #

radekc

geby: Readresult je v private - i v SVN, na to jsem tiše narážel v minulém článku :-D

radekc

25.10.2013 18:09:39 #

geby

No jo, mas recht. Opravim to.

A priste tise nenarazej, ale rovnou to rekni. :-p

geby

Komentování ukončeno

Naše nabídka

Partial English version.

MVP
Ing. Radek Červinka - Embarcadero MVP
profil na linkedin, Twitter:@delphicz

Nabízím placené poradenství a konzultace v oblasti programování a vývoje SW.
Dále nabízíme i vývoj speciálního software na zakázku.

Neváhejte nás kontaktovat (i ohledně reklamy nebo burzy práce).

Pokud chcete podpořit tento server libovolnou částkou, můžete použít PayPal. Moc děkuji.

Delphi Certified Developer

O Delphi.cz

Delphi je jediný moderní RAD nástroj podporující tvorbu nativních aplikací pro platformu Win32, Win64 , Mac OSX a na iPhone a Android (s výhledem na další platformy díky FireMonkey) na současném trhu (včetně Windows 8.1).

V současnosti je světová komunita přes dva miliónů vývojářů.

Delphi.cz je nezávislý portál pro uživatele Delphi. Portál není koncipován pro úplné začátečníky, i když i ti se zde nebudou nudit, ale spíše na programátory, kteří již něco znají a chtějí své znalosti dále rozvíjet a sledovat novinky.

Anketa

Poslední komentáře

Comment RSS