Отсылка e-mail через C#

Отсылка e-mail через C#Этот кусок кода показывает, как можно программно отослать электронное письмо средствами C#. Письмо отсылается через указанный SMTP-сервер, который требует авторизацию. Так же к письму можно прикрепить одно или несколько вложений.

//Адрес SMTP-сервера
String smtpHost = "SMTP.SERVER.RU";
//Порт SMTP-сервера
int smtpPort = 25;
//Логин
String smtpUserName = "LOGIN";
//Пароль
String smtpUserPass = "PASSWORD";

//Создание подключения
SmtpClient client = new SmtpClient(smtpHost, smtpPort);
client.Credentials = new NetworkCredential(smtpUserName, smtpUserPass);

//Адрес для поля "От"
String msgFrom = "LOGIN@SERVER.RU";
//Адрес для поля "Кому" (адрес получателя)
String msgTo = "KUDA@TO.RU";
//Тема письма
String msgSubject = "Письмо от C#";
//Текст письма
String msgBody = "Привет!\r\n\r\nЭто тестовое письмо\r\n\r\n--\r\nС уважением, C# :-)";
//Вложение для письма
//Если нужно больше вложений, для каждого вложения создаем свой объект Attachment с нужным путем к файлу
Attachment attachData = new Attachment("D:\Тестовое вложение.zip");

//Создание сообщения
MailMessage message = new MailMessage(msgFrom, msgTo, msgSubject, msgBody);
//Крепим к сообщению подготовленное заранее вложение
message.Attachments.Add(attachData);

try
{
    //Отсылаем сообщение
    client.Send(message);
}
catch (SmtpException ex)
{
    //В случае ошибки при отсылке сообщения можем увидеть, в чем проблема
    Console.WriteLine(ex.InnerException.Message.ToString());
}

Тело письма и его заголовок будут созданы и отосланы в кодировке — UTF-8. Если же захочется отослать письмо в кодировке windows-1251, то начинаются проблемы. Мне удалось найти только одно достаточно подробное описание этой проблемы. И в более свежих версиях фреймворка (помимо .Net Framework 2.0) точно такая же проблема.

Да, отчасти помогает прописывание вручную заголовка, говорящего о кодировке всего письма, но с другой стороны, не так уж все радужно. Итак, перед кодом отсылки сообщения (после 29-ой строки) добавляем следующий код:

message.SubjectEncoding = Encoding.Default;
message.BodyEncoding = Encoding.Default;
message.Headers["Content-type"] = "text/plain; charset=windows-1251";

В итоге мы имеем письмо, у которого два раза (второй раз вставляется автоматом) повторяется заголовок Content-type, в первом указана кодировка windows-1251, а во втором настырный .Net вставляет UTF-8. Не думаю, что такое дублирование заголовков можно назвать корректным. Но это, похоже, единственный способ добиться какой-то адекватности отображения тела письма в почтовой программе, которая получит его.

Для просмотра писем я использую Mozilla Thunderbird. В нем тело письма отображается нормально, но при этом он не понимает кодировки заголовка письма и отображает его некорректно. Если оставить заголовок в кодировке UTF-8, а тело письма создавать в windows-1251:

message.BodyEncoding = Encoding.Default;
message.Headers["Content-type"] = "text/plain; charset=windows-1251";

Тогда Thunderbird правильно отображает письмо. Но все эти ухищрения достаточно сомнительны из-за того, что каждый почтовый клиент может по-своему трактовать двойной и противоречивый заголовок Content-type. Так, к примеру, если посмотреть на такое письмо через вэбовский клиент mail.ru, то при любых раскладах тело письма отображается коряво. Тема же отображается правильно, только если отсылать ее в UTF-8.

Глядя на эту печальную картину, в голову приходит только два варианта решения проблемы. Плюнуть и отсылать письма в UTF-8 или же самостоятельно подключаться к SMTP-серверу и «вручную общаться» с ним по протоколу SMTP, а не использовать готовую реализацию в виде класса SmtpClient.

Автор

  • SMO

    Часть кода спамерской проги, очень полезно 😉

  • Хм, ну если все спамеры начнут вот так вот (с авторизацией через smtp сервер) рассылать письма, то возможно наступит золотой век и их всех можно будет легко обнаружить 😈

    Этим способом можно разве что какому-то кругу людей что-то рассылать, на что они дали согласие, и то, это мероприятие будет достаточно сомнительным. Куда прогрессивнее думать в сторону RSS, в таком случае…

  • Вячеслав

    😥 У меня что то с авторизацией не получается пробывал через smtp.mail.ru а так же smtp.yandex.ru поле from у меня совпадает с логином как требует того безопасность серверов пароль верный отправить не получается ругается на авторизацию Хелп…

  • SID

    Отправка через известные смтп сервера не поддерживается, тк они закрыты.. лучше поиши менее известные, тогда точно должна отсылка работать…вот этим пользуюсь gawab.com

  • SASHALOM

    👿
    Неработает
    String msgFrom = «LOGIN@SERVER.RU»;

    пишет что недопустимый символ в електронной почте!
    Что мне делать? И как правильно прописать електронную почту?
    Заранее спасибо

  • Добрый день!
    Статья супер, но вот такой вопрос, а можно ли обойтись без сторонних серверов для отправки email сообщения?

  • chapluck

    если добавить строку
    message.Headers[«Content-type»] = «text/plain; charset=windows-1251»;
    и письмо содержит attachments, их содержимое определяется как обычный текст на некоторых клиентах (например в веб-интерфейсе rambler)
    Заменил на
    message.Headers[«Content-type»] = «;сharset=windows-1251»;
    Тогда аттачменты для всех клиентов, с которыми я работал отображаются корректно.
    …и мое мнение: SmtpClient связывает разработчика по рукам и ногам! довольно скудный класс, предоставляющий минимальный интерфейс даже в методах protected! лучше сразу от него отказаться и использовать уже опенсоурсные смтп-клиенты

  • У меня пишет : Недопустимый знак в заголовке электронной почты.

    Что делать ?

  • SnowMan

    а у меня не авторизовывается, блин делал на C++ Builder все замечательно работало, но тему письма в нормаьный вид не смог привести а тут блин вообще лажа ни чего не работает, говорит подленность не пройдена, хотя все нормально работает на smtp.mail.ru я просто в шоке, немогу понят что такое.. может кто сталкивался…
    ПС Пароль и логин я точно знаю и ввести его не правльно я не мог..

  • yannik

    Я тут сваял простенький отправлятель почты по SMTP
    не обессудьте если что.
    Но проверил — ВСЕ работает
    вот выложил сюда больше под руку никуда не попалось

  • yannik,
    Извините, но ссылку я удалил, так как намного актуальнее были бы выложенные исходники (проект), а не exe файл. Ведь мало ли, может эта программка пароли куда-то еще шлет от мыла 🙂 А исходники может кому и пригодились бы для обучения.

  • Уже лет пять пользуюсь своей процедурой рассылки. Работает идеально со всеми почтовиками, как в Win32 так и в Web
    public static bool SendMail(string fromaddress, string toaddress, string subject, string messagestr)
    {
    System.Configuration.Configuration config = WebConfigurationManager.OpenWebConfiguration(HttpContext.Current.Request.ApplicationPath);
    System.Net.Configuration.MailSettingsSectionGroup settings = (System.Net.Configuration.MailSettingsSectionGroup)config.GetSectionGroup(«system.net/mailSettings»);
    MailAddress from;
    if (settings.Smtp.From != null)
    {
    from = new MailAddress(fromaddress);
    }
    else
    {
    return false;
    }
    MailMessage message = new MailMessage();
    message.From = from;
    if (!toaddress.Contains(«;»))
    {
    message.To.Add(new MailAddress(toaddress));
    }
    else
    {
    foreach (string tAddress in toaddress.Split(new string[] { «;» }, StringSplitOptions.RemoveEmptyEntries))
    {
    if (tAddress != «»)
    {
    message.To.Add(new MailAddress(tAddress));
    }
    }
    }
    message.Subject = subject;
    message.Body = messagestr;
    message.BodyEncoding = System.Text.Encoding.GetEncoding(«KOI8-R»);
    message.SubjectEncoding = System.Text.Encoding.GetEncoding(«KOI8-R»);
    message.IsBodyHtml = true;
    SmtpClient client = new SmtpClient();
    System.Net.NetworkCredential authuser = new System.Net.NetworkCredential(settings.Smtp.Network.UserName, settings.Smtp.Network.Password);
    client.Host = settings.Smtp.Network.Host;
    if (MainClass.IsNumeric(settings.Smtp.Network.Port.ToString()))
    {
    client.Port = Convert.ToInt32(settings.Smtp.Network.Port);
    }
    else
    {
    client.Port = 25;
    }
    if (settings.Smtp.Network.UserName != string.Empty && settings.Smtp.Network.Password != string.Empty)
    {
    client.Credentials = authuser;
    }
    else
    {
    client.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
    }
    client.Send(message);
    return true;
    }

  • Станислав

    Я понимаю что торможу но, интересно насколько вот такое извращение, имеет право на жизнь:
    class mrcEncoder:Encoding
    {
    private Encoding enc;
    private mrcEncoder()
    {}
    public mrcEncoder(int codePage): base(codePage)
    {
    enc = Encoding.GetEncoding(codePage);
    //this.codePage=codePage
    }
    public override string BodyName
    {
    get
    {
    if (base.CodePage == 1251) return «windows-1251»;
    return base.BodyName;
    }
    }
    public override int GetByteCount(char[] a, int b, int c)
    {
    return enc.GetByteCount(a, b, c);
    }
    public override int GetCharCount(byte[] a, int b, int c)
    {
    return enc.GetCharCount(a, b, c);
    }
    public override int GetChars(byte[] a, int b, int c,char [] d,int e)
    {
    return enc.GetChars(a, b, c,d,e);
    }
    public override int GetBytes(char[] a, int b, int c, byte[] d, int e)
    {
    return enc.GetBytes(a, b, c, d, e);
    }
    public override int GetMaxCharCount(int a)
    {
    return enc.GetMaxCharCount(a);
    }
    public override int GetMaxByteCount(int a)
    {
    return enc.GetMaxByteCount(a);
    }
    }
    и соответственно
    MailMessage mail = new MailMessage(«LOGIN@SERVER.RU», «kuda@to.ru»);
    mrcEncoder enc=new mrcEncoder(1251);
    mail.SubjectEncoding = enc;
    mail.Subject = «Письмо в win-1251»;
    mail.BodyEncoding = enc;
    mail.IsBodyHtml = false;
    mail.Body = «Текст письма»;
    SmtpClient snd = new SmtpClient(«SMTP.SERVER.RU», 25);
    snd.Send(mail);

  • Александр

    Я для тестирования отправки использую тестовый smtp, как здесь: http://grantorinoteam.blogspot.com/2010/04/smtp-python.html.
    Он никуда ничего не посылает, зато все логирует.

  • Юрий

    Автору спасибо!!! Всё работает!!! +5 (хотя можно и +10) за труды!!!

  • Александр

    Windows — 7 Проф. Framework 4.0, VS 2010 Express. Название компа — NIX. Файлы конфигурации не трогал. Приложение — консольное. Запускал в режиме отладки.

    Программы рассылки аналогичны для 4-х smtp серверов, за исключением их названий и портов — у yahoo — 465, у yandex и mail.ru — 25. В результате 3 разные виды, или подвиды ошибок (ниже). Вообще мне надо разослать 11 тыс. писем по строго определённому списку (в этом смысле — не спам — жаловаться по идее никто не должен). Что посоветуете?
    Заранее спасибо!

    using System;

    using System.IO;

    using System.Web;

    using System.Net;

    using System.Net.Mail;

    using System.Net.Mime;

    public class SendGmail2

    {

    static void Main()

    {

    StreamWriter log_out;

    try { log_out = new StreamWriter(@»E:\Send\logfile_Gmail2.txt»); }

    catch (IOException exc) { Console.WriteLine(«Error Opening Log File»); Console.WriteLine(exc); return; }

    Console.SetOut(log_out);

    try

    {

    SmtpClient Smtp = new SmtpClient(«smtp.gmail.com», 587);

    Smtp.EnableSsl = true;

    Smtp.DeliveryMethod = SmtpDeliveryMethod.Network;

    Smtp.UseDefaultCredentials = false;

    Smtp.Credentials = new System.Net.NetworkCredential(«newleft1990@gmail», «Password»);

    MailMessage Message = new MailMessage();

    MailAddress from = new MailAddress(«newleft1990@gmail.com», «Абрамович Александр Борисович», System.Text.Encoding.UTF8);

    Message.From = from;

    string someArrows = new string(new char[] { ‘\u2190’, ‘\u2191’, ‘\u2192’, ‘\u2193’ }),

    body = «Успешная проверка программы рассылки.» + Environment.NewLine + someArrows,

    subject = «Химкинский лес, лагерь, общероссийские референдум и Движение» + someArrows;

    Message.Subject = subject;

    Message.Body = body;

    Message.SubjectEncoding = System.Text.Encoding.UTF8;

    Message.BodyEncoding = System.Text.Encoding.UTF8;

    Message.Priority = MailPriority.High;

    string file = @»D:\Политика\Химлес\контакты.txt»;

    Attachment attach = new Attachment(file, MediaTypeNames.Application.Octet);

    ContentDisposition disposition = attach.ContentDisposition;

    disposition.CreationDate = File.GetCreationTime(file);

    disposition.ModificationDate = File.GetLastWriteTime(file);

    disposition.ReadDate = File.GetLastAccessTime(file);

    Message.Attachments.Add(attach);

    Message.To.Add(new MailAddress(«nlrf@mail.ru»));

    try { Smtp.Send(Message); }

    catch (SmtpException exc) { Console.WriteLine(exc); }

    Smtp.Dispose();

    }

    catch (IOException exc) { Console.WriteLine(«Error Writing Log File»); Console.WriteLine(exc); }

    log_out.Close();

    }

    }

    ?

    System.Net.Mail.SmtpException: Серверу SMTP требовалось защищенное
    соединение, или подлинность клиента не была установлена.
    Ответ сервера: 5.5.1 Authentication Required. Learn more at
    в System.Net.Mail.MailCommand.CheckResponse
    (SmtpStatusCode statusCode, String response)
    в System.Net.Mail.MailCommand.Send
    (SmtpConnection conn, Byte[] command, String from)
    в System.Net.Mail.SmtpTransport.SendMail
    (MailAddress sender, MailAddressCollection recipients,
    String deliveryNotify, SmtpFailedRecipientException& exception)
    в System.Net.Mail.SmtpClient.Send(MailMessage message)
    в SendGmail2.Main() в E:\Send\SendGmail2\SendGmail2\Program.cs:строка 45

    System.Net.Mail.SmtpException: Сбой при отправке сообщения электронной
    почты. —> System.IO.IOException: Не удается прочитать данные из
    транспортного соединения: Удаленный хост принудительно разорвал
    существующее подключение. —> System.Net.Sockets.SocketException:
    Удаленный хост принудительно разорвал существующее подключение
    в System.Net.Sockets.Socket.Receive
    (Byte[] buffer, Int32 offset, Int32 size, SocketFlags socketFlags)
    в System.Net.Sockets.NetworkStream.Read
    (Byte[] buffer, Int32 offset, Int32 size)
    — Конец трассировки внутреннего стека исключений —
    в System.Net.Sockets.NetworkStream.Read
    (Byte[] buffer, Int32 offset, Int32 size)
    в System.Net.DelegatedStream.Read
    (Byte[] buffer, Int32 offset, Int32 count)
    в System.Net.BufferedReadStream.Read
    (Byte[] buffer, Int32 offset, Int32 count)
    в System.Net.Mail.SmtpReplyReaderFactory.ReadLines
    (SmtpReplyReader caller, Boolean oneLine)
    в System.Net.Mail.SmtpReplyReaderFactory.ReadLine
    (SmtpReplyReader caller)
    в System.Net.Mail.SmtpConnection.GetConnection(ServicePoint servicePoint)
    в System.Net.Mail.SmtpTransport.GetConnection(ServicePoint servicePoint)
    в System.Net.Mail.SmtpClient.GetConnection()
    в System.Net.Mail.SmtpClient.Send(MailMessage message)
    — Конец трассировки внутреннего стека исключений —
    в System.Net.Mail.SmtpClient.Send(MailMessage message)
    в Sendyahoo.Main() в E:\Send\Sendyahoo\Sendyahoo\Program.cs:строка 45

    System.Net.Mail.SmtpException: Сбой при отправке сообщения электронной
    почты. —> System.FormatException: Недопустимый знак в заголовке
    электронной почты: ‘В’.
    в System.Net.Mime.MailBnfHelper.GetTokenOrQuotedString
    (String data, StringBuilder builder)
    в System.Net.Mime.ContentDisposition.ToString()
    в System.Net.Mime.ContentDisposition.PersistIfNeeded
    (HeaderCollection headers, Boolean forcePersist)
    в System.Net.Mime.MimeBasePart.get_Headers()
    в System.Net.Mime.MimePart.Send(BaseWriter writer)
    в System.Net.Mime.MimeMultiPart.Send(BaseWriter writer)
    в System.Net.Mail.Message.Send(BaseWriter writer, Boolean sendEnvelope)
    в System.Net.Mail.SmtpClient.Send(MailMessage message)
    — Конец трассировки внутреннего стека исключений —
    в System.Net.Mail.SmtpClient.Send(MailMessage message)
    в SendYandex2.Main() в E:\Send\SendYandex2\SendYandex2\Program.cs:строка 45

    mail.ru — полная аналогия с yandex.ru

  • Александр

    Вопрос к Igamerу: нельзя ли было бы привести полный тестовый код, а не только один метод? Или хотя бы помочь разобраться с двумя операторами (общий смысл, а также какие «юзинги» нужны для первого, и как в MainClass определять переменную второго).

    System.Configuration.Configuration config = WebConfigurationManager.OpenWebConfiguration
    (HttpContext.Current.Request.ApplicationPath);

    if (MainClass.IsNumeric(settings.Smtp.Network.Port.ToString()))

    Большое спасибо.

  • Работает! Автору большое спасибо!

  • Хацкер

    Тема не говно, автор молодец.

  • Stas

    скажите, если smtp-сервре не требует авторизации, то достаточно опустить строку client.Credentials = new NetworkCredential(smtpUserName, smtpUserPass); или требуется явное указание в каком-то свойстве SmtpClient ?

  • Stas

    Вопросов не имею…
    Некоторые серверы SMTP требуют проверки подлинности клиента до отправки электронной почты сервером от своего имени. Чтобы использовать сетевые учетные данные по умолчанию, можно задать для свойства UseDefaultCredentials значение true вместо установки данного свойства. Если для свойства UseDefaultCredentials задано значение false, то значение, установленное для свойства Credentials, будет использоваться в качестве учетных данных при подключении к серверу. Если для свойства UseDefaultCredentials задано значение false и свойство Credentials не задано, то почта будет отправлена на сервер анонимно.