
Отправка письма из VB6 через CDO: SMTP-сервер, авторизация и вложения
CDO есть в каждой Windows начиная с 2000, поэтому письмо из VB6 можно отправить без сторонних компонентов. Главное правильно заполнить конфигурацию SMTP и не забыть про кодировку.
Что такое CDO и почему не MAPI
У программы на VB6 есть два стандартных пути отправить письмо. Элементы MAPI работают через почтовый клиент пользователя: нужен настроенный Outlook, а сам Outlook может показать предупреждение безопасности. Для отчётов, уведомлений и рассылок из служебной программы это неудобно.
CDO для Windows 2000 (он же CDOSYS, библиотека cdosys.dll) работает напрямую с SMTP-сервером. Ему не нужен ни почтовый клиент, ни профиль пользователя: достаточно адреса сервера, порта и, если требуется, логина с паролем. Библиотека входит в состав Windows, начиная с 2000 и XP, так что отдельно её устанавливать не надо.
Подключить её можно ссылкой Microsoft CDO for Windows 2000 Library, тогда работают подсказки IntelliSense и именованные константы. А можно обойтись поздним связыванием через CreateObject, как в примерах ниже: так код не зависит от версии библиотеки и переносится в VBScript почти без изменений.
Минимальная отправка через SMTP
Письмо описывается объектом CDO.Message, а параметры доставки хранятся в коллекции Configuration.Fields. Поля адресуются длинными URI из пространства имён schemas.microsoft.com. Выглядит громоздко, но набор всегда одинаковый, его удобно вынести в отдельную функцию.
Значение sendusing = 2 означает «отправлять через SMTP по сети». Значение 1 использует локальную папку Pickup службы IIS SMTP, которой на рабочих станциях обычно нет, поэтому оно почти никогда не нужно.
Private Const SCH As String = "http://schemas.microsoft.com/cdo/configuration/"
Public Sub SendMail(ByVal strTo As String, ByVal strSubject As String, _
ByVal strHtml As String, Optional ByVal strFile As String)
Dim msg As Object
Set msg = CreateObject("CDO.Message")
With msg.Configuration.Fields
.Item(SCH & "sendusing") = 2 ' через SMTP по сети
.Item(SCH & "smtpserver") = "smtp.example.ru"
.Item(SCH & "smtpserverport") = 465
.Item(SCH & "smtpusessl") = True ' неявный SSL
.Item(SCH & "smtpauthenticate") = 1 ' basic-авторизация
.Item(SCH & "sendusername") = "robot@example.ru"
.Item(SCH & "sendpassword") = GetMailPassword() ' не храните в коде
.Item(SCH & "smtpconnectiontimeout") = 30
.Update
End With
msg.From = "Reports <robot@example.ru>" ' имя и адрес отправителя
msg.To = strTo
msg.Subject = strSubject
msg.HTMLBody = strHtml
msg.HTMLBodyPart.Charset = "utf-8" ' кириллица в теле
msg.BodyPart.Charset = "utf-8" ' и в теме
If Len(strFile) > 0 Then msg.AddAttachment strFile
msg.Send
End SubКодировка и кириллица
Без явной кодировки CDO кодирует письмо в системной кодовой странице, и у получателя с другой локалью тема превращается в вопросительные знаки. Свойство Charset у BodyPart влияет на заголовки, включая тему, а у HTMLBodyPart и TextBodyPart на соответствующие части тела. Самый совместимый вариант сегодня это utf-8 во всех трёх местах.
Если отправляете простой текст, используйте TextBody вместо HTMLBody и задавайте TextBodyPart.Charset. Если задать только HTMLBody, CDO сам сгенерирует текстовую альтернативу, и почтовые клиенты без поддержки HTML покажут её.
Вложения, копии и важность
AddAttachment принимает полный путь к файлу и сам определяет MIME-тип по расширению. Метод можно вызвать несколько раз. Для получателей копии есть свойства CC и BCC, несколько адресов перечисляются через точку с запятой или запятую.
Иногда нужно, чтобы письмо приходило с пометкой важности или с адресом для ответа, отличным от отправителя. Для этого есть свойство ReplyTo и поля заголовков в коллекции msg.Fields.
- msg.CC = "boss@example.ru" и msg.BCC = "archive@example.ru" для копий;
- msg.ReplyTo = "support@example.ru", чтобы ответы шли в поддержку, а не роботу;
- msg.Fields("urn:schemas:httpmail:importance") = 2 и затем msg.Fields.Update для высокой важности;
- msg.CreateMHTMLBody "file:///C:/report/index.html" превратит готовую HTML-страницу с картинками в тело письма.
Обработка ошибок отправки
Метод Send работает синхронно: программа ждёт, пока сервер не примет письмо или не откажет. При отказе возникает ошибка с номером вроде -2147220975, а в описании обычно есть ответ SMTP-сервера, по которому легко понять причину. Оборачивайте отправку в обработчик и пишите описание в журнал.
Самые частые причины сбоев:
- «The transport failed to connect to the server»: неверный адрес или порт, либо исходящие соединения на 25 и 465 закрыты брандмауэром или провайдером;
- «The server rejected the sender address»: адрес From не совпадает с учётной записью, от имени которой идёт авторизация;
- ошибка 535 или «authentication failed»: неверный пароль либо почтовый сервис требует пароль приложения;
- сбой на этапе SSL при верных настройках: старая Windows не поддерживает TLS 1.2, которого требуют современные серверы.
Private Sub cmdSend_Click()
On Error GoTo ErrSend
Screen.MousePointer = vbHourglass
SendMail txtTo.Text, "Отчёт за " & Format$(Date, "dd.mm.yyyy"), _
"<p>Отчёт во вложении.</p>", App.Path & "\report.xls"
Screen.MousePointer = vbDefault
MsgBox "Письмо отправлено", vbInformation
Exit Sub
ErrSend:
Screen.MousePointer = vbDefault
LogError "SendMail", Err.Number, Err.Description ' своя процедура журнала
MsgBox "Не удалось отправить письмо:" & vbCrLf & Err.Description, vbExclamation
End SubSSL, порты и ограничения CDO
Флаг smtpusessl в CDO надёжно работает в режиме неявного SSL на порту 465: шифрованное соединение устанавливается сразу. С портом 587 и командой STARTTLS поведение зависит от версии Windows, поэтому для внешних почтовых сервисов выбирайте 465, если сервер его поддерживает.
Шифрование выполняет системный компонент SChannel. На Windows 7 и Server 2008 R2 протокол TLS 1.2 включается обновлениями и ключами реестра, на XP его нет вовсе. Если программа внезапно перестала отправлять письма после того, как провайдер почты отключил старые протоколы, причина именно в этом, а не в коде.
Второе ограничение серьёзнее: CDO умеет только логин и пароль. Крупные почтовые сервисы постепенно отключают такую авторизацию и требуют OAuth 2.0, которого в CDO нет. Для них остаётся корпоративный SMTP-ретранслятор внутри сети, который принимает письма от приложения и сам доставляет их дальше.
Как это делают сегодня
Вопросы и ответы
Нужно ли устанавливать CDO на компьютеры пользователей?
Нет. CDOSYS входит в Windows начиная с 2000 и XP. Устаревший CDONTS и CDO 1.21 из состава Office это другие библиотеки, их в новом коде использовать не стоит.
Почему тема письма приходит знаками вопроса?
Не задана кодировка заголовков. Установите msg.BodyPart.Charset = "utf-8" до вызова Send, а для HTML-тела ещё и HTMLBodyPart.Charset.
Можно ли отправить письмо через Gmail или Microsoft 365?
Только если сервис ещё разрешает вход по паролю приложения. Когда требуется OAuth 2.0, CDO не подойдёт: отправляйте через собственный SMTP-ретранслятор или вызывайте внешнюю утилиту на .NET с MailKit.