Ответы с форумов MSDN

Прямая отправка писем по протоколу SMTP в C#

Date: 06.04.2017 18:07:17

Ни один из общедоступных SMTP серверов, определенно, не позволяет отправлять почту с произвольным адресом в поле "от кого". Вам нужно либо иметь свой SMTP-сервер, либо использовать для каждого заказчика свой аккаунт.

Message 851

Date: 07.04.2017 14:33:00

Вы не привели никакой информации об архитектуре разрабатываемого приложения. Я понятия не имею, что обычно пишут на VB.NET. Если речь идет о веб-сайте на своем сервере, вы просто развертываете на нем любое подходящее ПО (IIS умеет это, можно установить какой-нибудь Small HTTP Server). Если же это обычное настольное приложение, без всякой серверной части, то все не так просто.

Message 850

Date: 08.04.2017 17:07:35

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

- предоставить пользователям самим ввести параметры SMTP-сервера, пользуясь любым публичным почтовым сервисом (почта же у всех есть)

- вместе с приложением устанавливать SMTP-сервер на целевую машину, и слать этим сервером

- не использовать Net.Mail.Smtp, а слать письма "напрямую" по TCP серверу получателя, имитируя работу SMTP-сервера

- найти почтовый сервис, который предоставляет API для отправки почты (скорее всего не бесплатно)

Message 849

Date: 08.04.2017 18:53:21

1. Они же в почтовой программе вводят эти параметры. В справке по почте они должны быть. На предприятии им администратор введет, а для домашних пользователей просто объяснить где эти параметры искать.

2. По поводу установки SMTP-сервера, хотел посоветовать Small HTTP Server, но их сайт лежит на данный момент, так что я даже не знаю. Не могу посоветовать ничего больше того, что вы сами поисковиком найдете.

3. По поводу отправки напрямую, см здесь.

4. По поводу API для отправки почты, опять же не могу посоветовать ничего, кроме того что гуглиться

Message 848

Date: 08.04.2017 20:37:41

Понятия не имею, что такое "открытый почтовый релей". Обычно отправка почты происходит так: почтовый клиент отправляет письмо "серверу отправки" (обычно, с аутентификацией), затем сервер отправки отправляет письмо целевому серверу (без аутентификации). Смысл этого кода - отправить письмо напрямую целевому серверу, без сервера отправки, что позволяет впихнуть что угодно в поле "От кого". Другое дело, что письма с сомнительных IP-адресов будут скорее всего улетать в спам (так же как в спам улетают письма с серверов местных провайдеров), так что этот подход может быть не лучшим на практике.

Message 847

Date: 09.04.2017 9:15:05

В общем, попробовал на практике, действительно это работает, но письма улетают в спам. Рассказываю подробнее, с реализацией на C#, кому нужны другие языки думаю сможете перевести.

Чтобы отправить письмо, нужно сначала определить целевой SMTP-сервер (Именно для входящей почты, а не тот, который обслуживает пользователей. Например, для mail.ru это mxs.mail.ru, хотя пользователей обслуживает smtp.mail.ru) Это делается так:
1. Взять из адреса все, что после @
2. Запросить у DNS-сервера MX запись

Для получения MX записи возьмем класс отсюда: https://social.msdn.microsoft.com/Forums/vstudio/en-US/89b21138-596f-4efc-8e86-d440d260c41e/how-to-find-smtp-mail-server-name-and-port?forum=csharpgeneral

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace WinFormsTest
{
    public class Mx
    {
        public Mx()
        {
        }
        [DllImport("dnsapi", EntryPoint = "DnsQuery_W", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
        private static extern int DnsQuery([MarshalAs(UnmanagedType.VBByRefStr)]ref string pszName, QueryTypes wType, QueryOptions options, int aipServers, ref IntPtr ppQueryResults, int pReserved);

        [DllImport("dnsapi", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern void DnsRecordListFree(IntPtr pRecordList, int FreeType);

        public static string[] GetMXRecords(string domain)
        {

            IntPtr ptr1 = IntPtr.Zero;
            IntPtr ptr2 = IntPtr.Zero;
            MXRecord recMx;
            if (Environment.OSVersion.Platform != PlatformID.Win32NT)
            {
                throw new NotSupportedException();
            }
            ArrayList list1 = new ArrayList();
            int num1 = Mx.DnsQuery(ref domain, QueryTypes.DNS_TYPE_MX, QueryOptions.DNS_QUERY_BYPASS_CACHE, 0, ref ptr1, 0);
            if (num1 != 0)
            {
                throw new ApplicationException(num1.ToString());
            }
            for (ptr2 = ptr1; !ptr2.Equals(IntPtr.Zero); ptr2 = recMx.pNext)
            {
                recMx = (MXRecord)Marshal.PtrToStructure(ptr2, typeof(MXRecord));
                if (recMx.wType == 15)
                {
                    string text1 = Marshal.PtrToStringAuto(recMx.pNameExchange);
                    list1.Add(text1);
                }
            }
            Mx.DnsRecordListFree(ptr2, 0);
            return (string[])list1.ToArray(typeof(string));
        }

        private enum QueryOptions
        {
            DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE = 1,
            DNS_QUERY_BYPASS_CACHE = 8,
            DNS_QUERY_DONT_RESET_TTL_VALUES = 0x100000,
            DNS_QUERY_NO_HOSTS_FILE = 0x40,
            DNS_QUERY_NO_LOCAL_NAME = 0x20,
            DNS_QUERY_NO_NETBT = 0x80,
            DNS_QUERY_NO_RECURSION = 4,
            DNS_QUERY_NO_WIRE_QUERY = 0x10,
            DNS_QUERY_RESERVED = -16777216,
            DNS_QUERY_RETURN_MESSAGE = 0x200,
            DNS_QUERY_STANDARD = 0,
            DNS_QUERY_TREAT_AS_FQDN = 0x1000,
            DNS_QUERY_USE_TCP_ONLY = 2,
            DNS_QUERY_WIRE_ONLY = 0x100
        }

        private enum QueryTypes
        {
            DNS_TYPE_MX = 15
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct MXRecord
        {
            public IntPtr pNext;
            public string pName;
            public short wType;
            public short wDataLength;
            public int flags;
            public int dwTtl;
            public int dwReserved;
            public IntPtr pNameExchange;
            public short wPreference;
            public short Pad;
        }
    }
}

После получения имени сервера, нужно отправить ему необходимые данные по TCP. Возьмем слегка модифицированный класс SmtpDirect, см ниже. 

Пример использования:

private void button2_Click(object sender, EventArgs e)
        {
            /*textBox1 - содержит адрес получателя*/
            string[] elems = textBox1.Text.Split("@".ToCharArray());
            string[] arr = Mx.GetMXRecords(elems[1]);

            string s = "";
            foreach (string x in arr) s += x + Environment.NewLine;
            MessageBox.Show("MX records:"+Environment.NewLine+s);

            MailMessage msg = new MailMessage();
            msg.To = textBox1.Text;
            msg.From = "putin@cremlin.ru";
            msg.Subject = "Hello, world";
            msg.Body = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
            SmtpDirect.SmtpServer = arr[0];
            
            SmtpDirect.Send(msg);
            textBox2.Text = SmtpDirect.output.ToString();
        }


Результат: 


Сервер входящей почты обязан принять без аутентификации любой бред, который мы ему пришлем, т.к. по смыслу протокола кто угодно может отправить почту кому угодно (в том числе сервера местных провайдеров многочисленных уездных городов, которые невозможно рассортировать на "плохие" и "хорошие"). Единственное что он может, это запихнуть в спам подозрительную почту, например если по IP-адресу не находится ничего в Reverse DNS. Но если нажать кнопку "Не спам", то последующие такие сообщения в спам не улетают, так что схема рабочая.






Message 846

Date: 09.04.2017 13:40:18

Попробуйте через mailto, может получиться. Не знаю как там вложения обрабатываются. Не думаю, что это будет сильно удобнее для пользователей, чем ввод параметров в вашей программе.

Параметры subject, from, body задаются в свойствах класса MailMessage.

Для корректной обработки вложений класс SmtpDirect пришлось немного модифицировать (исправил заголовок Content-Disposition):

using System;
using System.Text;
using System.IO;
using System.Net.Sockets;
using System.Net;
using System.Net.NetworkInformation;
using System.Web.Mail;
using System.Windows.Forms;

namespace SmtpDirect
{
    /// <summary>
    /// provides methods to send email via smtp direct to mail server
    /// </summary>
    public class SmtpDirect
    {
        /// <summary>
        /// Get / Set the name of the SMTP mail server
        /// </summary>
        public static string SmtpServer;

        public static StringBuilder output;

        private enum SMTPResponse : int
        {
            CONNECT_SUCCESS = 220,
            GENERIC_SUCCESS = 250,
            DATA_SUCCESS = 354,
            QUIT_SUCCESS = 221
        }
        public static bool Send(MailMessage message)
        {
            output = new StringBuilder(300);
            IPHostEntry IPhst = Dns.Resolve(SmtpServer);
            IPEndPoint endPt = new IPEndPoint(IPhst.AddressList[0], 25);
            Socket s = new Socket(endPt.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            s.Connect(endPt);

            if (!Check_Response(s, SMTPResponse.CONNECT_SUCCESS))
            {
                s.Close();
                return false;
            }

            Senddata(s, "HELO example.com\r\n");
            output.AppendLine("HELO");

            if (!Check_Response(s, SMTPResponse.GENERIC_SUCCESS))
            {
                s.Close();
                return false;
            }

            Senddata(s, string.Format("MAIL From: {0}\r\n", message.From));
            output.AppendLine("MAIL From");
            if (!Check_Response(s, SMTPResponse.GENERIC_SUCCESS))
            {

                s.Close();
                return false;
            }

            string _To = message.To;
            string[] Tos = _To.Split(new char[] { ';' });
            foreach (string To in Tos)
            {
                Senddata(s, string.Format("RCPT TO: {0}\r\n", To));
                output.AppendLine("RCPT TO");
                if (!Check_Response(s, SMTPResponse.GENERIC_SUCCESS))
                {
                    s.Close();
                    return false;
                }
            }

            if (message.Cc != null)
            {
                Tos = message.Cc.Split(new char[] { ';' });
                foreach (string To in Tos)
                {
                    Senddata(s, string.Format("RCPT TO: {0}\r\n", To));
                    output.AppendLine("RCPT TO");
                    if (!Check_Response(s, SMTPResponse.GENERIC_SUCCESS))
                    {
                        s.Close();
                        return false;
                    }
                }
            }

            StringBuilder Header = new StringBuilder();
            Header.Append("From: " + message.From + "\r\n");
            Tos = message.To.Split(new char[] { ';' });
            Header.Append("To: ");
            for (int i = 0; i < Tos.Length; i++)
            {
                Header.Append(i > 0 ? "," : "");
                Header.Append(Tos[i]);
            }
            Header.Append("\r\n");
            if (message.Cc != null)
            {
                Tos = message.Cc.Split(new char[] { ';' });
                Header.Append("Cc: ");
                for (int i = 0; i < Tos.Length; i++)
                {
                    Header.Append(i > 0 ? "," : "");
                    Header.Append(Tos[i]);
                }
                Header.Append("\r\n");
            }
            Header.Append("Date: ");
            Header.Append(DateTime.Now.ToString("ddd, d M y H:m:s z"));
            Header.Append("\r\n");
            Header.Append("Subject: " + message.Subject + "\r\n");
            Header.Append("X-Mailer: SMTPDirect v1\r\n");
            string MsgBody = message.Body;
            if (!MsgBody.EndsWith("\r\n"))
                MsgBody += "\r\n";
            if (message.Attachments.Count > 0)
            {
                Header.Append("MIME-Version: 1.0\r\n");
                Header.Append("Content-Type: multipart/mixed; boundary=unique-boundary-1\r\n");
                Header.Append("\r\n");
                Header.Append("This is a multi-part message in MIME format.\r\n");
                StringBuilder sb = new StringBuilder();
                sb.Append("--unique-boundary-1\r\n");
                sb.Append("Content-Type: text/plain\r\n");
                sb.Append("Content-Transfer-Encoding: 7Bit\r\n");
                sb.Append("\r\n");
                sb.Append(MsgBody + "\r\n");
                sb.Append("\r\n");

                foreach (object o in message.Attachments)
                {
                    MailAttachment a = o as MailAttachment;
                    byte[] binaryData;
                    if (a != null)
                    {
                        FileInfo f = new FileInfo(a.Filename);
                        sb.Append("--unique-boundary-1\r\n");
                        sb.Append("Content-Type: application/octet-stream; file=" + f.Name + "\r\n");
                        sb.Append("Content-Transfer-Encoding: base64\r\n");
                        sb.Append("Content-Dispos"+"ition: attachment; filename=" + f.Name + "\r\n");
                        sb.Append("\r\n");
                        FileStream fs = f.OpenRead();
                        binaryData = new Byte[fs.Length];
                        long bytesRead = fs.Read(binaryData, 0, (int)fs.Length);
                        fs.Close();
                        string base64String = System.Convert.ToBase64String(binaryData, 0, binaryData.Length);

                        for (int i = 0; i < base64String.Length; )
                        {
                            int nextchunk = 100;
                            if (base64String.Length - (i + nextchunk) < 0)
                                nextchunk = base64String.Length - i;
                            sb.Append(base64String.Substring(i, nextchunk));
                            sb.Append("\r\n");
                            i += nextchunk;
                        }
                        sb.Append("\r\n");
                    }
                }
                MsgBody = sb.ToString();
            }

            Senddata(s, ("DATA\r\n"));
            output.AppendLine("DATA");
            if (!Check_Response(s, SMTPResponse.DATA_SUCCESS))
            {
                s.Close();
                return false;
            }
            Header.Append("\r\n");
            Header.Append(MsgBody);
            Header.Append(".\r\n");
            Header.Append("\r\n");
            Header.Append("\r\n");
            Senddata(s, Header.ToString());
            if (!Check_Response(s, SMTPResponse.GENERIC_SUCCESS))
            {
                s.Close();
                return false;
            }

            Senddata(s, "QUIT\r\n");
            output.AppendLine("QUIT");
            Check_Response(s, SMTPResponse.QUIT_SUCCESS);
            s.Close();
            return true;
        }
        private static void Senddata(Socket s, string msg)
        {
            byte[] _msg = Encoding.ASCII.GetBytes(msg);
            s.Send(_msg, 0, _msg.Length, SocketFlags.None);
        }
        private static bool Check_Response(Socket s, SMTPResponse response_expected)
        {
            string sResponse;
            int response;
            byte[] bytes = new byte[1024];
            while (s.Available == 0)
            {
                System.Threading.Thread.Sleep(100);
            }

            s.Receive(bytes, 0, s.Available, SocketFlags.None);
            sResponse = Encoding.ASCII.GetString(bytes);

            sResponse = sResponse.Replace("\0", "");
            output.Append(sResponse);

            response = Convert.ToInt32(sResponse.Substring(0, 3));
            if (response != (int)response_expected)
                return false;
            return true;
        }


    }
}


Пример отправки с вложением:

private void button1_Click(object sender, EventArgs e)
        {
            /*textBox1 - содержит адрес получателя*/
            string[] elems = textBox1.Text.Split("@".ToCharArray());
            string[] arr = Mx.GetMXRecords(elems[1]);

            string s = "";
            foreach (string x in arr) s += x + Environment.NewLine;
            MessageBox.Show("MX records:" + Environment.NewLine + s);

            MailMessage msg = new MailMessage();
            msg.To = textBox1.Text;//TO
            msg.From = "vasya@gmail.com";//FROM
            msg.Subject = "Lorem ipsum";//SUBJECT
            msg.Body = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";//BODY

            MailAttachment ma = new MailAttachment(@"D:\Users\SmallSoft\Documents\file.xls");//ATTACHMENT            
            msg.Attachments.Add(ma);

            SmtpDirect.SmtpServer = arr[0];

            SmtpDirect.Send(msg);
            textBox2.Text = SmtpDirect.output.ToString();
        }

"А насколько это универсально?"

Только тестирование может показать. Я попробовал отправлять между яндексом и мейлом, в принципе работает. Но проблема в том что иногда письмо может совсем отвергаться, а не просто в спам попадать, так что это способ только вспомогательный.

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

Что касается порта, то 25 это стандартный порт для SMTP. Все почтовые сервера должны его принимать.





Автор: VadimTagil

Главная страница - Список тем - Репозиторий на GitHub