уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

Приветствую всех читателей блога WebDelphi.ru! Очень надеюсь, что затянувшаяся вынужденная пауза в работе блога подходит к концу. И в скором времени те, кто ожидает продолжение постов про DevExpress и Google Календарь получат пищу для размышлений в виде статьи (черновой вариант уже недели две как пылится в блоге). Этот пост я решил снова посвятить библиотеке Synapse и снова рассмотреть несколько вопросов относительно работы с почтой. В качестве отправной точки для написания статьи я решил использовать тему с форума и один из последних комментариев в статье, посвященной использованию Synapse для работы с GMail.

Про Synapse я рассказываю в блоге уже довольно давно (и, что интересно, в «пиаре» Synapse обвинен ещё не был). И хоть и стараюсь обычно рассказывать так, чтобы было не только интересно и с картинками, но и понятно, вопросы все равно появляются самые разные. На некоторые вопросы отвечаю сразу, где-то сам долго «туплю» прежде, чем смогу найти решение, а какие-то вопросы по Synapse коплю до тех пор, пока не наберется «критическая масса» для отдельного поста и свободная минутка как, например, для этого поста.

Прежде всего, избегая вопросов и претензий типа «а у меня тут ошибка«, «а код не работает» и т.д. пишу жирными буквами и выделяю особо следующие моменты, которые следует учитывать при копипасте всего исходного кода, рассмотренного ниже:

      • используется последний на момент написания статьи релиз Synapse (Release 40, rev. 182)
      • для работы с почтой используются сервера mail.ru, yandex.ru и gmail.com. Работу с другими серверами проверяйте самостоятельно.
      • Весь исходный код написан в Delphi XE3 со всеми установленными апдейтами.
      • НЕТ я НЕ использую Delphi 7 и версии Delphi младше XE2. Нет, у меня нет установленной Delphi 7
      • Код проверялся на Windows 7

Для работы нам потребуются следующие модули Synapse: smtpsend, pop3send, blcksock, ssl_openssl, mimemess, mimepart, synautil, synachar.

Отправка писем с вложениями

Отправка писем с использованием Synapse рассматривалась в следующих статьях:

  1. Synapse. Отправка писем, используя SMTP.
  2. Synapse в Delphi. Отправка писем с вложениями.
  3. Synapse. Работа с почтой GMail.com

  4. Компонент для отправки почты с GMail.com.

Начнем с отправки c самого простого. Открываем Delphi (желательно XE3, т.к. см. выше), создаем новый проект VCL Application и бросаем на форму компоненты, как показано на рисунке ниже:

synapse mail

Вид главной формы приложения

Теперь разберемся как происходит отправка писем с помощью Synapse. По большому счёту, отправку любого письма (хоть с вложением, хоть без) выполняется в пять шагов, которые можно представить в виде вот такой небольшой таблички:

Операция Команды SMTP Метод TSmtpSend Примечание
Авторизация ESMTP EHLO или HELO
function Login:boolean
Установка обратного адреса MAIL FROM
function MailFrom(const Value: string; Size: Integer): Boolean;
Используется для отправки необработанных писем
Установка получателей сообщения RCPT TO
function MailTo(const Value: string): Boolean;
Если получателей несколько, то команда выполняется отдельно для каждого получателя
Отправка данных (тела сообщения) DATA
function MailData(const Value: Tstrings): Boolean;
Закрытие сессии QUIT
function Logout: Boolean;

Для того, чтобы облегчить нам работу с Synapse, разработчики написали несколько вспомогательных методов для отправки писем и поместили их все в тот же модуль SmtpSend.pas. В этих методах нет ничего «сверхъестественного», все они используют в работе TsmtpSend:

function SendToRaw(const MailFrom, MailTo, SMTPHost: string; const MailData: TStrings; const Username, Password: string): Boolean;
 
function SendTo(const MailFrom, MailTo, Subject, SMTPHost: string; const MailData: TStrings): Boolean;
 
function SendToEx(const MailFrom, MailTo, Subject, SMTPHost: string;const MailData: TStrings; const Username, Password: string): Boolean;

Удобно пользоваться этими функциями — пользуйтесь. Хочется, чтобы все было «по уму» — отображение прогресса отправки писем, лог работы клиента и все такое прочее — используем класс TSmtpSend без всяких функций-обёрток.

Сегодня мы будем пользоваться только классом TSmtpSend. Для начала, напишем обработчик события OnClick для кнопки «Проверить подключение». Вполне возможно, что, если Вы впервые сталкиваетесь с SMTP в Synapse, то могли проверять подключение так:

procedure Tfmain.btnLoginClick(Sender: TObject);
begin
  SMTPClient.TargetHost:=edHost.Text;
  SMTPClient.TargetPort:=edPort.Text;
  SMTPClient.UserName:=edLogin.Text;
  SMTPClient.Password:=edPassword.Text;
  SMTPClient.AutoTLS:=CheckBox1.Checked;
  if SMTPClient.Login then
    begin
      ShowMessage('Проверка соединения прошла успешно');
      SMTPClient.Logout;
    end
  else
    raise Exception.Create('Ошибка соединения с сервером');
end;

НО мы, используя код выше проверили только соединение с сервером (хотя и задали и логин и пароль пользователя), но не авторизацию пользователя на сервере. В итоге можно получить такую ситуацию: пользователь вводит правильный хост/порт/логин, но в пароле случайно допускает небольшую ошибочку. Ваша программа сообщает на проверке, что все в порядке, а письмо не отправляется…Чтобы такого не произошло и Вы были уверены, что заданные пользователем данный корректны, а письма будут отправляться

всегда проверяйте после выполнения Login свойство AuthDone.

То есть, окончательный вариант кода проверки соединения с сервером должен выглядеть так:

procedure Tfmain.btnLoginClick(Sender: TObject);
begin
  SMTPClient.TargetHost:=edHost.Text;
  SMTPClient.TargetPort:=edPort.Text;
  SMTPClient.UserName:=edLogin.Text;
  SMTPClient.Password:=edPassword.Text;
  SMTPClient.AutoTLS:=CheckBox1.Checked;
  if SMTPClient.Login then
    if SMTPClient.AuthDone then //--> проверили авторизацию пользователя
       begin
         ShowMessage('Проверка соединения прошла успешно');
         SMTPClient.Logout;
         Exit;
       end;
  raise Exception.Create('Ошибка соединения с сервером');
end;

Вот теперь можно двигаться далее. А далее пройдемся сразу по нескольким вопросам, которые в разное время задавались по несколько раз в разных постах разными людьми :)

Как отправить письмо с вложением нескольким адресатам?

Здесь, на самом деле, нет ничего сложного. Про то как собрать и закодировать письмо с вложением я рассказывал в статье «Synapse в Delphi. Отправка писем с вложениями«. И, если посмотреть внимательно на приведенный в статье код, то мы можем там увидеть такие строчки:

 Msg:=TMimeMess.Create;
  try
    Msg.Header.From:=edEmail.Text;
    Msg.Header.ToList.Add(edName.Text);//записали получателя
    Msg.Header.Subject:=edTheme.Text;
    [...]
  finally
    Msg.Free
  end;

В Synapse, как и полагается, список получателей — это действительно список. ToList — это обычный TStringList и создается без каких-либо изменений, т.е. в качестве разделителя используется запятая. Так что, никаких проблем, по факту-то нет. Если список получателей письма у вас также как и у меня  в программке пишется в Edit, то достаточно записать список строку с получателями вот так:

«Иван Иванов» <Ivan@mail.ru>, «Петя Петров» <Petya@mail.ru>

Или вообще так:

Ivan@mail.ru, Petya@mail.ru

И при отправке письма записать эту строку с свойство ToList вот так:

Msg.Header.ToList.DelimitedText:=edName.Text;

С адресами разобрались, теперь о самой отправке. Как было сказано в табличке выше, если получателей несколько, то на каждый адрес получателя должна выполняться команда MAIL TO. Так мы и сделаем. Пишем код обработчика OnClick для кнопки «Отправить»:

procedure Tfmain.btnSendClick(Sender: TObject);
var Msg : TMimeMess;
    MimePart: TMimePart;
    S,T: string;
    sended: boolean;
begin
  {задаем настройки для smtp}
  with SMTPClient do
    begin
      TargetHost:=edHost.Text;
      TargetPort:=edPort.Text;
      UserName:=edLogin.Text;
      Password:=edPassword.Text;
      AutoTLS:=CheckBox1.Checked;
    end;
  {собираем сообщение}
  Msg:=TMimeMess.Create;
  try
    Msg.Header.From:=edEmail.Text;//определяем отправителя
    Msg.Header.ToList.DelimitedText:=edName.Text;//список получателей
    Msg.Header.Subject:=edTheme.Text;//тема письма
 
    MimePart:=Msg.AddPartMultipart('mixed', nil);//создаем "корень" сообщения
    if rbHtml.Checked then //будем отправлять текст письма как HTML-код
       Msg.AddPartHTML(memText.Lines, MimePart)
    else
       Msg.AddPartText(memText.Lines, MimePart);//отправляем как Plain text
    {проверяем и добавляем вложения}
    if FileExists(edFile.Text) then 
       Msg.AddPartBinaryFromFile(edFile.Text,MimePart);
    if FileExists(edFile2.Text) then
       Msg.AddPartBinaryFromFile(edFile2.Text,MimePart);
    //кодируем
    Msg.EncodeMessage;
    //пробуем отправить
    if (SMTPClient.Login)and(SMTPClient.AuthDone) then //соединились с сервером и авторизовались
    begin
      //устанавливаем обратный адрес
      if SMTPClient.MailFrom(GetEmailAddr(Msg.Header.From), Length(Msg.Lines.Text)) then
      begin
        s := edName.Text;//получаем список получателей
        {для каждого получателя выполняем команду MAIL TO}
        repeat
          t := GetEmailAddr(Trim(FetchEx(s, ',', '"')));//получаем email-адрес 
          if t &lt;&gt; '' then //есть адрес
            sended := SMTPClient.MailTo(t); 
          if not sended then //не смогли выполнить команду - прерываем выполнение
            Break;
        until s = '';
        if sended then //все в порядке - выполняем команду DATA
           sended := SMTPClient.MailData(Msg.Lines);
       end;
      if sended then
        ShowMessage('Письмо успешно отправлено')
      else
        ShowMessage('Во время отправки письма произошла ошибка');
      SMTPClient.Logout;//уходим
    end;
  finally
    Msg.Free;
  end;
end;
Если у вас не отправляются письма через GMail, то положите библиотеки libeay32.dll и ssleay32.dll рядом с exe-файлом. 

К меня эти библиотеки прописались в системе уже где только можно было, поэтому у меня проблем с отправкой никаких нет, а у вас или у ваших пользователей проблемы могут возникнуть. Кстати, скачать dll-ки, которыми пользуюсь я можно со страницы с исходниками. Переходим к следующему вопросу.

Как отправлять копии писем (использовать поля CC и BCC в email)?

Вначале небольшая справка о том, чем отличается поле CC от BCC в Email.

To: — Имя и адрес получателя. Может содержаться несколько раз (если письмо адресовано нескольким получателям).
cc: (от англ. carbon copy). Содержит имена и адреса вторичных получателей письма, к которым направляется копия.
bcc: (от англ. blind carbon copy). Содержит имена и адреса получателей письма, чьи адреса не следует показывать другим получателям. Это поле обычно обрабатывается почтовым сервером (и приводит к появлению нескольких разных сообщений, у которых bcc содержит только того получателя, кому фактически адресовано письмо). Каждый из получателей не будет видеть в этом поле других получателей из поля bcc.

Есть определенные «правила хорошего тона» когда и какие поля следует заполнять, но их мы оставим в покое, т.к. сейчас речь про то как этими полями пользоваться вообще.

С полями To все понятно — все получатели у нас пишутся в свойство Header.ToList у класса TMimeMess. И этими полями мы пользовались выше при отправке писем.

С полями CC тоже проблем быть не должно — у того же класса есть свойство Header.ССList. Заполняется свойство также как и ToList.

Остается вопрос по тому как записать поля BCC? В принципе, я уже много-много раз говорил, но повторюсь ещё раз (повторение — мать учения) — Synapse очень простая библиотека. Достаточно взглянуть чуть по-внимательнее на класс TMessHeader (заголовки сообщения), чтобы увидеть такое свойство:

property CustomHeaders: TStringList read FCustomHeaders;

И никто нам не запрещает им пользоваться. Ради проверки, изменим в нашей процедуре отправки письма строку:

Msg.Header.ToList.DelimitedText:=edName.Text;

на такую

Msg.Header.CustomHeaders.Add('BCC: '+edName.Text);

и снова попробуем отправить письмо на несколько адресов. В итоге на каждый ящик, указанный в BCC придёт отдельная копия письма. Видео-ролик я выкладывать здесь уже не буду, просто поверьте, что это работает :) Двигаемся далее.

Как получать отчёты о доставке писем?

Вот этот вопрос уже по-интереснее. Дело в том, что есть как минимум три возможности  сделать доставку уведомлений:

  • с использованием заголовков типа Return-Receipt-To, Errors-To, Status и т.д. Все эти заголовки указаны в RFC 2076, но помечены как не стандартные и не рекомендуемые к использованию. Следовательно сервер может просто «не понимать», что от него хотят.
  • Использование заголовка Disposition-Notification-To, которому посвящен RFC 2298. Вроде бы и стандарт есть и использовать можно, но элементарная проверка без Delphi и Synapse показала, что то же mail.ru никаких уведомлений не получает, хотя при отправке писем и на yandex.ru и на gmail.com я ставил в интерфейсе mail.ru галку «С уведомлением»
  • Использовать DSN (Delivery Status Notifications). Опять же, этот механизм хоть и может использоваться почтовыми серверами, но используется он далеко не всегда…

В общем, в поисках ответа на этот вопрос я перерыл довольно не малое количество всяческих RFC, админских форумов и т.д. и  т.п., но абсолютно надежного, со 100%  гарантией способа получать уведомления о доставке писем не нашел — практически с каждым почтовым сервером приходится разбираться отдельно и выбирать тот или иной способ работы. Вот такая вот печалька

332173d4caba2134da9816d45a612a63

Но нет худа без добра. Зато появилась возможность по-глубже закопаться в Synapse и посмотреть как можно «прикрутить» использование DSN к TSMTPSend.

Итак, как использовать DSN? Для того, чтобы мы могли воспользоваться этой возможностью, чтобы получать уведомления о доставке/задержке/ошибке сообщения мы, в первую очередь обязаны убедиться в том, что сервер поддерживает DSN.

Чтобы запросить дополнительные возможности SMTP-сервера мы можем отправить на сервер команду EHLO. Если сервер понимает эту команду, то он вернет нам список дополнительных расширений, например, максимальный размер сообщения (SIZE), способы авторизации пользователя и, в том числе, «скажет» поддерживается ли DSN.

Я уже говорил, что Synapse это очень простая библиотека? По-моему говорил. Дело в том, что команда EHLO используется при выполнении метода Login у TSmtpSend. И, если сервер поддерживает эту команду, то заполняется свойство:

property ESMTPcap: TStringList read FESMTPcap;

В котором и содержатся все поддерживаемые сервером расширения. Если сервер не поддерживает EHLO, то список будет пуст. Посмотрим, что «скажут» о себе сервера mail.ru, yandex.ru и gmail.com.

Ответ mail.ru

SIZE 73400320
8BITMIME
AUTH PLAIN LOGIN
STARTTLS

Поддержки DSN нет.

Ответ Gmail.com

SIZE 35882577
8BITMIME
AUTH LOGIN PLAIN XOAUTH XOAUTH2
ENHANCEDSTATUSCODES

Поддержки DSN нет.

Ответ Yandex.ru

8BITMIME
PIPELINING
SIZE 42991616
STARTTLS
AUTH LOGIN PLAIN
DSN
ENHANCEDSTATUSCODES

Есть поддержка DSN.

Оказывается наш отечественный почтовик по количеству дополнительных «фич» по-круче gmail будет…ну ладно. Из приведенных выше ответов становится понятно, что далее мы будем работать с SMTP yandex.ru, т.к. на других серверах тестить DSN бесполезно — не поддерживается. 

Определившись с сервером переходим к главному — как получать уведомления о состоянии нашего письма? Открываем RFC 3461 и начинаем вникать в суть вопроса. Буквально в первых абзацах RFC нам говорят, что DSN расширяет команды RCPT и MAIL четырьмя дополнительным параметрами. Так, например, чтобы получить отчёт о доставке письма адресату от нас требуется включить в команду RCPT параметр NOTIFY=SUCCESS, чтобы получить отчёт об ошибке — NOTIFY=FAILURE и т.д. А можем и вообще включить все возможные значения параметра NOTIFY, например, так:

RCPT TO:<Dana@Ivory.EDU> NOTIFY=SUCCESS,FAILURE,DELAY

Проблема в том, что у TSMTPSend есть только один метод, отсылающий на сервер команду RCPT и называется он MailTo. Менять исходник Synapse — не серьезно. Но Synapse простая библиотека, а Delphi уже довольно давно позволяет нам расширять классы без всяких наследников — через хэлперы. Этой возможностью Delphi мы и воспользуемся — напишем простенький class helper для класса TSMTPSend.

Несмотря на то, что я буду писать хэлпер, Вам никто не запрещает сделать своего наследника для класса TSmtpSend, например, если вы работаете в Delphi 7

Итак, возвращаемся из RFC в Delphi и пишем в главном (и единственном) модуле нашего приложения такой хэлпер:
объявляем хэлпер

type
  TSMTPClientHelper = class helper for TSMTPSend
    function MailToEx(const AEmail:string; ANotifyString: string):boolean;
end;

пишем код метода MailToEx:

function TSMTPClientHelper.MailToEx(const AEmail: string;
  ANotifyString: string): boolean;
var S: string;
begin
  Sock.SendString('RCPT TO:&lt;' + AEmail + '&gt; ' +ANotifyString + CRLF);
  repeat
    s := Sock.RecvString(FTimeout);
    if Sock.LastError &lt;&gt; 0 then
      Break;
  until Pos('-', s) &lt;&gt; 4;
  Result := StrToIntDef(Copy(s, 1, 3), 0) div 100 = 2;
end;

Здесь стоит обратить внимание на один важным момент. Дело в том, что

при выполнении любого метода у TSmtpSend заполняются такие поля как FResultCode, FFullResult и т.д., по которым мы, в случае чего, можем провести диагностику работы, например, определить почему письмо не дошло до получателя. В нашем же хэлпере мы просто определяем выполнилась ли команда или нет, не заполняя при этом поля TSmtpSend.

Сейчас же нам диагностика по большому счёту не требуется, нам надо научиться получать уведомление о доставке. Поэтому теперь возвращаемся в обработчик кнопки «Отправить» и меняем строку:

sended := SMTPClient.MailTo(t);

на вот такую

sended := SMTPClient.MailToEx(t,'NOTIFY=SUCCESS,FAILURE');

Вот и всё. Теперь при отправке письма с почты Яндекса мы будем получать уведомления о том, что письмо доставлено адресату или, если при доставке произошла ошибка.

Уведомление сообщает нам, что письмо именно доставлено, но ничего не говорит о том прочитано ли доставленное письмо или нет
не вздумайте использовать DSN, если он не поддерживается сервером — 100% получите ошибку при отправке. Всегда проверяйте наличие записи DSN в свойстве ESMTPcap

С DSN чуть-чуть разобрались, но как быть, например, с mail.ru, который DSN не поддерживает? На этот вопрос у меня пока ответ один — используйте заголовки о которых я говорил выше и надейтесь на то, что принимающий сервер их понимает и всячески поддерживает…хотя этот вариант очень не надежный, но другого у меня нет.

Ну вот. С отправкой писем более менее разобрались. Как видите, ничего сверхсложного нет, все, о чем было рассказано выше, работает. Переходим к следующему моменту работы с почтой в Synapse.

Визуализация отправки почты

Что касается обработки событий у TSMTPSend, то она ничем не отличается от той же обработки событий у другого класса Synapse — THTTPSend. Про обработку событий я также рассказывал несколько раз:

  1. Synapse. Скачиваем данные с ProgressBar’ом.
  2. Библиотека Synapse. Работа с модулем HTTPSend.pas.

Что тут можно добавить? Визуализировать абсолютно весь процесс отправки письма с помощью заполнения того же ProgressBar’а можно, но не нужно. Поясню почему для тех, кто хоть убейся, но желает вытащить в progressbar все до последнего байта. Если Вы внимательно прочитали все, что сказано выше, то наверняка уже усвоили, что перед тем как мы начнем отправлять на сервер тело письма (выполнять команду DATA) мы выполняем ещё ряд команд типа EHLO/HELO, RCPT, MAIL и т.д. Да, эти команды «жрут» трафик, но сколько? Пару килобайт от силы (если только вы не занимаетесь спамом и не шлете свое письмо сразу миллиарду получателей) так не проще ли все эти небольшие команды просто выводить в тот же лог или просто выводить текст в тот же Label типа «подключаемся к серверу», «отправляем команду X на сервер Y» и т.д? По-моему, вполне нормальная практика. А вот уже непосредственную отправку письма весом, скажем в несколько мегабайт визуализировать стОит. Остается только вовремя назначить обработчик события и вовремя же его убрать. И заниматься визуализацией у нас будет, также как и в этой статье, обработчик события OnStatus.

Теперь от слов к делу. Открываем Delphi и пишем вот такую процедуру:

type
  Tfmain = class(TForm)
   [...]
  private
    procedure OnStatusEvent(Sender: TObject; Reason: THookSocketReason; const Value: String);
  public
end;
 
procedure Tfmain.OnStatusEvent(Sender: TObject; Reason: THookSocketReason;
  const Value: String);
begin
    if Reason=HR_WriteCount then
       ProgressBar1.Position:=ProgressBar1.Position+StrToInt(Value)
end;

Всё. Кто-то ожидал чего-то жутко навороченного? Сорри, если разочаровал :) Теперь назначаем обработчик. Чтобы наш ProgressBar не «дёргался» при каждой отправки данных на сервер, обработчик надо назначить непосредственно перед выполнением метода MailData и освободить после выполнения метода. То есть возвращаемся в обработчик кнопки «Отправить» и дописываем четыре новых строки:

[...]
 if SMTPClient.MailFrom(GetEmailAddr(Msg.Header.From), Length(Msg.Lines.Text)) then
      begin
        ProgressBar1.Max:=Length(Msg.Lines.Text);//максимальное значение - размер уже собранного письма
        ProgressBar1.Position:=0;//сбрасываем позицию прогрессбара на ноль
        [...]
        if sended then
          begin
            SMTPClient.Sock.OnStatus:=OnStatusEvent;//назначили обработчик
            sended := SMTPClient.MailData(Msg.Lines);
            SMTPClient.Sock.OnStatus:=nil;//убрали обработчик
          end;
[...]

Больше ничего не надо. Запускаем приложение, создаем письмо с большим вложением и отправляем — увидите, что прогресс бар заполняется идеально ровно.

В этой же части статьи стоит обратить внимание (уже наверное в тысячный раз) на один момент работы Synapse о котором почему-то приходится постоянно напоминать новичкам, предъявляющим претензии к работе Synapse:

Synapse реализует идеологию синхронной работы с сокетами. И это отнюдь не потому, что разработчик библиотеки не в курсе, что есть в Windows ещё и асинхронная работа. Причина в том, что Synapse — это не только очень простая, но и кроссплатформенная библиотека, а в Unix-системах, на сколько я знаю поддерживается только блокирующий (синхронный) режим работы.

Отсюда и все якобы «проблемы» с Synapse, что дескать при работе с Synapse программа «зависает». А как должна вести себя программа при синхронной работе? Именно, что «висеть» пока не закончится работа метода Synapse. Про синхронную и асинхронную работу с сокетами очень емко и толково рассказано, кстати, в книге «Глубины Indy« (Indy тоже, кстати, использует синхронную работу). Те, кто до сих пор считает, что Synapse работает не правильно и «вешает» программу — очень советую скачать эту книгу и зазубрить наизусть главу «Блокирующий режим против неблокирующего«, а потом скачать книгу про Delphi и заучить наизусть другую главу — про потоки (TThread) и уже потом начинать работать над программой. Это не злая шутка, говорю абсолютно серьезно — прочитайте! Очень сильно пригодиться в дальнейшем, тем более, что ссылочку я дал на русский перевод книги. Мне по, крайней мере, в свое время эта книга очень хорошо помогла вникнуть в основы работы с Сетью вообще. Ну, а если лень читать книги, то тогда взять в руки набор компонентов ICS и радоваться жизни — эти компоненты работают асинхронно.

С визуализациями тоже вроде бы разобрались. Переходим к следующему моменту работы с почтой — диагностике работы TSmtpSend.

Диагностика работы TSmtpSend.

Для правильной диагностики работы с SMTP нам опять же никуда не деться без RFC. Для работы нам потребуются RFC 821 — здесь содержится информация об SMTP вообще и о кодах статуса в частности, а также RFC 1893 — содержит информацию о расширенных кодах состояния (Enhanced Mail System Status Codes).

Чтобы разобраться где какие коды используются сделаем в нашей программе следующее — напишем обработчик для события сокета OnReadFilter. Обработчик очень простой:

procedure Tfmain.OnDataFilterEvent(Sender: TObject; var Value: AnsiString);
begin
  memLog.Lines.Add(Value)
end;

Теперь назначим этот обработчик:

procedure Tfmain.FormCreate(Sender: TObject);
begin
  SMTPClient:=TSMTPSend.Create;
  SMTPClient.Sock.OnReadFilter:=OnDataFilterEvent;
end;

Теперь попробуем отправить письмо, скажем, с mail.ru на yandex.ru. Получим вот такой лог работы:

220 smtp20.mail.ru ESMTP ready
250-smtp20.mail.ru
250-SIZE 73400320
250-8BITMIME
250-AUTH PLAIN LOGIN
250 STARTTLS
220 2.0.0 Start TLS
250-smtp20.mail.ru
250-SIZE 73400320
250-8BITMIME
250 AUTH PLAIN LOGIN
235 Authentication succeeded
250 OK
250 Accepted
354 Enter message, ending with «.» on a line by itself
250 OK id=1UFpz1-0007UK-0Y
221 smtp20.mail.ru closing connection

Здесь представлены все ответы сервера. Первые три цифры в ответе — это обычный код статуса, информацию по каждому из которых можно получить из RFC 821.
Код статуса пишется после выполнения каждой команды в свойство ResultCode TSmtpSend:

property ResultCode: Integer read FResultCode;

Теперь попробуем сделать наоборот — отправим письмо с yandex.ru на mail.ru. Как было показано выше, Yandex помимо всего прочего поддерживает отправку расширенных кодов состояния (строка ENHANCEDSTATUSCODES в списке). Вот как будет выглядеть лог в этом случае:

220 smtp2.mail.yandex.net ESMTP (Want to use Yandex.Mail for your domain? Visit http://pdd.yandex.ru)
250-smtp2.mail.yandex.net
[…] 250-PIPELINING
250-SIZE 42991616
250-STARTTLS
250-AUTH LOGIN PLAIN
250-DSN
250 ENHANCEDSTATUSCODES
235 2.7.0 Authentication successful.
250 2.1.0 <bvv36@yandex.ru> ok
250 2.1.5 <vlad383@mail.ru> recipient ok
354 Enter mail, end with «.» on a line by itself
250 2.0.0 Ok: queued on smtp2.mail.yandex.net as 7hgu804R-7kginmdR
221 2.0.0 Closing connection.

Обратите внимание на строки, начиная с 235 2.7.0 Authentication successful.. Вторая тройка чисел, разделенная точками — это и есть расширенный код. Эти коды помогают нам, в случае необходимости, более детально разобраться в проблеме при отправке писем. Если сервер поддерживаем расширенные коды, то слаться они начинают, начиная с момента авторизации пользователя и вплоть до закрытия сессии. Информацию по расширенным кодам черпаем из RFC 1893. Для хранения расширенных кодов у TSMTPSend используются сразу несколько свойств:

 property EnhCode1: Integer read FEnhCode1;
 property EnhCode2: Integer read FEnhCode2;
 property EnhCode3: Integer read FEnhCode3;

Эти свойства также меняют свои значения каждый раз после выполнения команды, результат которой содержит расширенный код. Для того, чтобы получить описание расширенного кода у TSMTPSend имеется специальный метод:

function EnhCodeString: string;

Этот метод использует значения свойств EnhCode1, EnhCode2 ,EnhCode3 и возвращает строку с описанием кода. Вызывается метод Вами, соответственно, сразу же после выполнения команды. К примеру, если мы вызовем метод EnhCodeString сразу же после выполнения метода MailTo, то получим такое описание:

Success-Destination mailbox address valid

Точно также можно диагностировать работу и других методов TSmtpSend или, если необходимо, то анализировать самостоятельно все коды.

Ну вот, пожалуй и все, о чем хотелось рассказать сегодня. Тридцать девятая статья про Synapse в блоге WebDelphi.ru закончена. Впору уже какой-нибудь сборник статей что ли издавать…Спонсоры найдутся? ;).   Шучу.

Всем удачной работы с Synapse и до новых встреч!

Книжная полка

Описание: Рассмотрены практические вопросы по разработке клиент-серверных приложений в среде Delphi 7 и Delphi 2005 с использованием СУБД MS SQL Server 2000, InterBase и Firebird. Приведена информация о теории построения реляционных баз данных и языке SQL. Освещены вопросы эксплуатации и администрирования СУБД.
купить книгу delphi на ЛитРес
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
купить книгу delphi на ЛитРес
0 0 голоса
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
25 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
Dozent
Dozent
14/03/2013 16:48

Влад,великолепная статья. Вы затронули проблему с которой я столкнулся. Да возможно нет никакой проблемы, просто перечислить всех получателей прикрепить файлы и сделать рассылку из листа. Но Зачем каждому получателю видеть куда ещё уходило письмо?
Поэтому я пытался организовать рассылку индивидуально каждому из списка рассылки…

P.S. Продолжу читать статью, остановился чтобы написать комент…

Александр
Александр
28/03/2013 16:55

А как поступить если на сервере нет АВТОРИЗАЦИИ (да вот такая вот штука) т.е.
250-PIPELINING
250-SIZE 42991616
250-STARTTLS
250-DSN
250 ENHANCEDSTATUSCODES
как видим AUTH нет , в Indy у IdSMTP есть свойство AuthenticationType, которое как я понимаю можно установить в atNone в таких случаях, а как быть с synapse? Просто если пройти по шагам код функции Login (в smtpsend),там происходит поиск AUTH (s := FindCap(‘AUTH ‘)) и естественно ничего не находит и делает Logout.

Dozent
Dozent
30/03/2013 17:37

Простите 2 недели не занимался проектом, были другие дела сейчас опять приступил и ни как не могу понять вот этот код:

procedure Tfmain.btnLoginClick(Sender: TObject);
begin
SMTPClient.TargetHost:=edHost.Text;
SMTPClient.TargetPort:=edPort.Text;
SMTPClient.UserName:=edLogin.Text;
SMTPClient.Password:=edPassword.Text;
SMTPClient.AutoTLS:=CheckBox1.Checked;
if SMTPClient.Login then
if SMTPClient.AuthDone then //—> проверили авторизацию пользователя
begin
ShowMessage(‘Проверка соединения прошла успешно’);
SMTPClient.Logout;
Exit;
end;
raise Exception.Create(‘Ошибка соединения с сервером’);
end;

SMTPClient что это? где описанО?

Dozent
Dozent
30/03/2013 19:13

Всё перемешалось… Попытался пройтись по всем статьям связаным с этой темой, элементарно отправка письма без компанента не получается, хочется работать только с библиотекой… Да я понимаю что это мои заморочки и незнание:)

P.S. Было бы очен ьхорошо сделать одну статью в которой будит от начало и до конца и с проектиком рабочим… Это будит просто великолемно.

Dozent
Dozent
30/03/2013 19:53
Ответить на  Vlad

SMTPClient.TargetHost:=’smtp.gmail.com’;
SMTPClient.TargetPort:=’587′;

При выполнении, на шаге присвоения порта выдаёт ошибку доступа к памяти, библиотеки добавлены опенссл в юсес подключён

Dozent
Dozent
30/03/2013 22:01
Ответить на  Vlad

Всё норм, я просто глупый и не создал обьект
SMTPClient:=TSMTPSend.Create;

Dozent
Dozent
30/03/2013 22:02
Ответить на  Vlad

Вообще удалит эту цепочку коментов, ибо это только мои глупости)

Dozent
Dozent
31/03/2013 18:31

Влад, добрался до CC и BCC. При отправке получается следующее
«кому: скрытая копия: мне, скрытая копия: Andrey»
Т.е. накладывается маска, но получатель всё таки видит скольким людям ещё ушло такое письмо, это не совсем я считаю правильно и для идивидуальной рассылке придётся отправлять письмо каждому в отдельной рассылке, что как мне кажется утяжелит работу кода…
Какой-то выход ещё можете подсказать?

Dozent
Dozent
01/04/2013 00:50
Ответить на  Vlad

Не не я о другом говорю.
«кому: скрытая копия: мне, скрытая копия: Andrey» — Вот такую строку я вижу в списке получателе на почте!

Т.е. получается, что получатель видит что письмо было разослано не только ему, но и ещё комуто, хоть и не видит кому точно!!!
Я сделал расылку на две свои почты, всё дошло без проблем (указал сразу два адреса) и в обоих я вижу что письмо пришло не только на одну из моих почт, но и на другую тоже, вижу что ещё есть получатель, хоть и не вижу его адреса!

«кому: скрытая копия: мне, скрытая копия: Andrey»

Tolyan249
Tolyan249
02/04/2013 20:37

Спасибо за статью ,ну очень познавательна.

osiris11
osiris11
07/05/2013 11:07

можно ли отправить письмо с вложением получать уведомление о доставке?
отправляю так…


aMIMEPart : TMimePart;
Msg : TMimeMess;

Msg := TMimeMess.Create;
aMIMEPart := Msg.AddPartMultipart('alternate',nil);
Msg.AddPartBinaryFromFile(FileForSend.Text, aMIMEPart);
smtpsend.SendToRaw(FromMail.Text, ToMail.Text, HostSMTP.Text, Msg.Lines, LoginSMTP.Text, PassSMTP.Text);
Msg.Free;

или можно отправлять вложение используя MailToEx ?

osiris11
osiris11
08/05/2013 11:06

Спасибо, разобрался.
возникла другая проблема, используя функцию:
sended := SMTPClient.MailToEx(t,’NOTIFY=SUCCESS,FAILURE’);

возвращается:
501 <bla-bla-bla@yandex.ru> NOTIFY=SUCCESS,FAILURE,DELAY: missing or malformed local part (expected word or «<")
при этом письмо не уходит

Bahonty
Bahonty
08/11/2013 13:00

Дайте плиззз рабочий исходник, не получается самому, ибо мои руки кривые((

Евгений
Евгений
19/11/2013 08:48

Спасибо за статью.
Столкнулся с такой проблемой. Пишу ISAPI расширение, которое должно отправлять сообщения на почту после успешной регистрации пользователя на сайте. Если использовать, например, mail.ru (т.е. без SSL/TLS), то письма отправляются. С gmail — ни в какую. Волшебные dll’ки лежат рядом с приложением (тоже dll’кой) в рабочей папке IIS 7. Может их надо куда то в другое место положить…

Евгений
Евгений
19/11/2013 08:57

Таки кинул dll’ки в SysWOW64 и отправка с gmail заработала. Только что то меня это не радует. Потом то это ISAPI расширение надо будет на хостинг кидать и, наверное, возможности поместить dll’ки куда нужно не будет.

Angvelem
Angvelem
06/01/2015 07:40

Влад, зачем использовать repeat в отправке сообщений, Не проще использовать for?

if SMTPClient.MailFrom(GetEmailAddr(Msg.Header.From), Length(Msg.Lines.Text)) then
for I := 0 to Msg.Header.ToList.Count - 1 do
begin
Result := Integer(SMTPClient.MailTo(Msg.Header.ToList[I]));
if not Result then
Break;
end;

Alex
04/07/2015 21:05

За статью большое СПАСИБО!!! Проглотил и не заметил….:)
Но есть вопрос или точнее:
Как отправить письмо так чтобы копия письма осталась в папочке ОТПРАВЛЕННЫЕ на серваке (с вложениями)?