从.NET发送大量电子邮件

时间:2014-08-11 05:36:35

标签: c# asp.net email

我们有一个相当大的应用程序,该应用程序的一部分需要每天发送大约30万封电子邮件。

在.net中执行此操作非常慢,为了解决这个问题,我们确实使用了线程,这会使整个服务器崩溃。

我确定有更大的应用发送更多电子邮件。

在合理的时间范围内发送大量电子邮件的最佳方式是什么。

这是代码

第1部分主题发送

System.Threading.Tasks.Task.Run(() =>
{
    System.Net.Mail.SmtpClient smtp = new System.Net.Mail.SmtpClient();
    Utility.sendTemplatedEmail(model.email, "noreply@fakedomain.com", "New Jobs!", fullHtml: fullHtml, checkUnsubscribed: false, smtpClient: smtp);
    smtp.Dispose();

    lock (syncObject) 
    {
        emailCounter++;
        writer.WriteLine(string.Format("sent and smtp client closed: {0} -- email number: {1}", DateTime.UtcNow.ToString("dd/MM/yyyy hh:mm:ss.fff tt"), emailCounter.ToString()));

        writer.Flush();
        if (emailCounter == numberOfGroupedMatches) 
        {
            writer.Close();
            System.IO.File.Move(Server.MapPath("~/Temp/") + "emailLog.txt", Server.MapPath("~/Temp/") + "emailLog-Finished.txt");
        }
    }
});

第2部分实际发送

fullHtml = (fullHtml == null ? ViewToString("~/View/Shared/_DocumentTemplate.cshtml", controllerContext, new emailModel { body = body, subject = subject }) : fullHtml);
messageMail.Body = fullHtml;
System.Net.Mail.SmtpClient smtp = (smtpClient == null ? new System.Net.Mail.SmtpClient() : smtpClient);
smtp.Send(messageMail);

2 个答案:

答案 0 :(得分:1)

假设您使用的是内置的.Net System.Net.Mail.SmtpClient库,我建议您考虑制作自己的或使用其他第三方库。我还处理从.Net Windows服务生成的大量电子邮件......当我停止让微软为我发送邮件时,我的里程大幅增加。

  1. LumiSoft.Net.SMTP.Client是一个可靠的库,表现良好
  2. 以下类是我用作"第一选择",它非常轻量级......但是可能会也可能不会与您发送到的邮件服务器一起使用。在我的情况下,我将所有的SMTP流量传递给SendGrid,因此它节省了大量的CPU周期。原作者:http://www.codeproject.com/Articles/9637/SMTP-MailMessage-done-right

    使用System; 使用System.Collections; 使用System.Diagnostics; 使用System.IO; 使用System.Net; 使用System.Net.Sockets; 使用System.Text; 使用System.Text.RegularExpressions; 使用System.Net.Mail;

    命名空间General.Utilities.Mail {     公共类SMTPSendRawMIME     {     #region构造函数     public SMTPSendRawMIME()     {

    }
    #endregion
    
    //The following methods are KNOWN to be flawed... they work only with some SMTP Servers... not many.... 
    //On the other hand, they run very fast... so they are worth using when possible like with SendGrid
    #region Send Raw Mime
    
    #region Static Send Methods
    public static void SendEmail(SmtpClient Server, string FromEmail, string ToEmail, byte[] MIMEMessage) //bool SSL, 
    {
        SMTPSendRawMIME objMailClient = new SMTPSendRawMIME();
        objMailClient.Send(Server, FromEmail, ToEmail, MIMEMessage);
    }
    
    public static void SendEmail(General.Utilities.Mail.MailTools.MailServerTypes enuUseServerType, string FromEmail, string ToEmail, byte[] MIMEMessage) //bool SSL, 
    {
        SmtpClient objServer = General.Utilities.Mail.MailTools.GetMailServer(enuUseServerType);
        SMTPSendRawMIME objMailClient = new SMTPSendRawMIME();
        objMailClient.Send(objServer, FromEmail, ToEmail, MIMEMessage);
    }
    #endregion
    
    public void Send(SmtpClient Server, string FromEmail, string ToEmail, byte[] MIMEMessage) //bool SSL, 
    {
        if (Server.Credentials != null)
        {
            //if (SSL)
                //SendSSL(Server.Host, Server.Port, ((NetworkCredential)Server.Credentials).UserName, ((NetworkCredential)Server.Credentials).Password, FromEmail, ToEmail, MIMEMessage);
            //else
                Send(Server.Host, Server.Port, ((NetworkCredential)Server.Credentials).UserName, ((NetworkCredential)Server.Credentials).Password, FromEmail, ToEmail, MIMEMessage);
        }
        else
        {
            SendToOpenRelay(Server.Host, Server.Port, FromEmail, ToEmail, MIMEMessage);
        }
    
    }
    
    #region BASE Send
    /// <summary>
    /// Sends the message via a socket connection to an SMTP relay host. 
    /// </summary>
    /// <param name="hostname">Friendly-name or IP address of SMTP relay host</param>
    /// <param name="port">Port on which to connect to SMTP relay host</param>
    /// <param name="username"></param>
    /// <param name="password"></param>
    public void Send(string HostName, int Port, string UserName, string Password, string FromEmail, string ToEmail, byte[] MIMEMessage)
    {
        const int bufsize = 1000;
        TcpClient smtp;
        NetworkStream ns;
        int cb, startOfBlock;
        byte[] recv = new byte[bufsize];
        byte[] data;
        string message, block;
    
        try
        {
    
            smtp = new TcpClient(HostName, Port);
            ns = smtp.GetStream();
            cb = ns.Read(recv, 0, recv.Length);
            message = Encoding.ASCII.GetString(recv);
            System.Diagnostics.Debug.WriteLine(message, "Server");
            message = "EHLO\r\n";
            System.Diagnostics.Debug.WriteLine(message, "Client");
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
        }
        catch(Exception ex)
        {
            throw new Exception(string.Format("Unable to establish SMTP session with {0}:{1}", HostName, Port), ex);
        }
    
        try
        {
    
            //figure out the line containing 250-AUTH
            cb = ns.Read(recv, 0, recv.Length);
            message = Encoding.ASCII.GetString(recv);
            //System.Diagnostics.Debug.WriteLine(message, "Server");
            startOfBlock = message.IndexOf("250-AUTH");
            block = message.Substring(startOfBlock, message.IndexOf("\n", startOfBlock) - startOfBlock);
            //check the auth protocols
            if (-1 == block.IndexOf("LOGIN"))
                throw new Exception("Mailhost does not support LOGIN authentication");
    
            message = "AUTH LOGIN\r\n";
            //System.Diagnostics.Debug.WriteLine(message, "Client");
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
            clearBuf(recv);
            cb = ns.Read(recv, 0, recv.Length);
            message = Encoding.ASCII.GetString(recv);
            //System.Diagnostics.Debug.WriteLine(message, "Server");
            if (!message.StartsWith("334"))
                throw new Exception(string.Format("Unexpected reply to AUTH LOGIN:\n{0}", message));
    
            message = string.Format("{0}\r\n", Convert.ToBase64String(Encoding.ASCII.GetBytes(UserName)));
            //System.Diagnostics.Debug.WriteLine(message, "Client (username)");
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
            clearBuf(recv);
            cb = ns.Read(recv, 0, recv.Length);
            message = Encoding.ASCII.GetString(recv);
            //System.Diagnostics.Debug.WriteLine(message, "Server");
            if (!message.StartsWith("334"))
                throw new Exception(string.Format("Unexpected reply to username:\n{0}", message));
    
            message = string.Format("{0}\r\n", Convert.ToBase64String(Encoding.ASCII.GetBytes(Password)));
            //System.Diagnostics.Debug.WriteLine(message, "Client (password)");
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
            clearBuf(recv);
            cb = ns.Read(recv, 0, recv.Length);
            message = Encoding.ASCII.GetString(recv);
            //System.Diagnostics.Debug.WriteLine(message, "Server");
            if (message.StartsWith("535"))
                throw new Exception("Authentication unsuccessful");
            if (!message.StartsWith("2"))
                throw new Exception(string.Format("Unexpected reply to password:\n{0}", message));
    
            message = string.Format("MAIL FROM: <{0}>\r\n", FromEmail);
            //System.Diagnostics.Debug.WriteLine(message, "Client");
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
            clearBuf(recv);
            cb = ns.Read(recv, 0, recv.Length);
            message = Encoding.ASCII.GetString(recv);
            //System.Diagnostics.Debug.WriteLine(message, "Server");
            if (!message.StartsWith("250"))
                throw new Exception(string.Format("Unexpected reply to MAIL FROM:\n{0}", message));
    
            message = string.Format("RCPT TO: <{0}>\r\n", ToEmail);
            string strRcptToMessage = message;
            //System.Diagnostics.Debug.WriteLine(message, "Client");
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
            clearBuf(recv);
            cb = ns.Read(recv, 0, recv.Length);
            message = Encoding.ASCII.GetString(recv);
            //System.Diagnostics.Debug.WriteLine(message, "Server");
            if (!message.StartsWith("250"))
                throw new Exception(string.Format("Unexpected reply to RCPT TO:\n{0}", message + " (" + strRcptToMessage + ")"));
    
            message = "DATA\r\n";
            //System.Diagnostics.Debug.WriteLine(message, "Client");
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
            clearBuf(recv);
            cb = ns.Read(recv, 0, recv.Length);
            message = Encoding.ASCII.GetString(recv);
            //System.Diagnostics.Debug.WriteLine(message, "Server");
            if (!message.StartsWith("354"))
                throw new Exception(string.Format("Unexpected reply to DATA:\n{0}", message));
    
            //message = payload + "\r\n.\r\n";
            //data = Encoding.ASCII.GetBytes(Encoding.UTF8.GetString(MIMEMessage) + "\r\n.\r\n");
            data = MIMEMessage;
            ns.Write(data, 0, data.Length);
    
            message = "\r\n.\r\n";
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
    
            clearBuf(recv);
            cb = ns.Read(recv, 0, recv.Length);
            message = Encoding.ASCII.GetString(recv);
            //System.Diagnostics.Debug.WriteLine(message, "Server");
            if (!message.StartsWith("250"))
                throw new Exception(string.Format("Unexpected reply to end of data marker (\\r\\n.\\r\\n):\n{0}", message));
    
            message = "QUIT\r\n";
            //System.Diagnostics.Debug.WriteLine(message, "Client");
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
    
        }
        catch (Exception ex)
        {
            General.Utilities.Debugging.Report.SendError("SMTP Communication Error", ex);
            throw new Exception(string.Format("SMTP Communication Error: {0}", message), ex);
        }
        finally
        {
            if (null != smtp) smtp.Close();
        }
    }
    #endregion
    
    #region BASE SendSSL
    /// <summary>
    /// Sends the message via a socket connection to an SMTP relay host. 
    /// </summary>
    /// <param name="hostname">Friendly-name or IP address of SMTP relay host</param>
    /// <param name="port">Port on which to connect to SMTP relay host</param>
    /// <param name="username"></param>
    /// <param name="password"></param>
    private void SendSSL(string HostName, int Port, string UserName, string Password, string FromEmail, string ToEmail, byte[] MIMEMessage)
    {
        //This Doesn't Work Yet... :(
    
        const int bufsize = 1000;
        TcpClient smtp;
        System.Net.Security.SslStream ns;
        int cb, startOfBlock;
        byte[] recv = new byte[bufsize];
        byte[] data;
        string message, block;
    
        try
        {
    
            smtp = new TcpClient(HostName, Port);
            ns = new System.Net.Security.SslStream(smtp.GetStream(), true);
            cb = ns.Read(recv, 0, recv.Length);
            message = Encoding.ASCII.GetString(recv);
            System.Diagnostics.Debug.WriteLine(message, "Server");
            message = "EHLO\r\n";
            System.Diagnostics.Debug.WriteLine(message, "Client");
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
        }
        catch(Exception ex)
        {
            throw new Exception(string.Format("Unable to establish SMTP session with {0}:{1}", HostName, Port), ex);
        }
    
        try
        {
    
            //figure out the line containing 250-AUTH
            cb = ns.Read(recv, 0, recv.Length);
            message = Encoding.ASCII.GetString(recv);
            System.Diagnostics.Debug.WriteLine(message, "Server");
            startOfBlock = message.IndexOf("250-AUTH");
            block = message.Substring(startOfBlock, message.IndexOf("\n", startOfBlock) - startOfBlock);
            //check the auth protocols
            if (-1 == block.IndexOf("LOGIN"))
                throw new Exception("Mailhost does not support LOGIN authentication");
    
            message = "AUTH LOGIN\r\n";
            System.Diagnostics.Debug.WriteLine(message, "Client");
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
            clearBuf(recv);
            cb = ns.Read(recv, 0, recv.Length);
            message = Encoding.ASCII.GetString(recv);
            System.Diagnostics.Debug.WriteLine(message, "Server");
            if (!message.StartsWith("334"))
                throw new Exception(string.Format("Unexpected reply to AUTH LOGIN:\n{0}", message));
    
            message = string.Format("{0}\r\n", Convert.ToBase64String(Encoding.ASCII.GetBytes(UserName)));
            System.Diagnostics.Debug.WriteLine(message, "Client (username)");
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
            clearBuf(recv);
            cb = ns.Read(recv, 0, recv.Length);
            message = Encoding.ASCII.GetString(recv);
            System.Diagnostics.Debug.WriteLine(message, "Server");
            if (!message.StartsWith("334"))
                throw new Exception(string.Format("Unexpected reply to username:\n{0}", message));
    
            message = string.Format("{0}\r\n", Convert.ToBase64String(Encoding.ASCII.GetBytes(Password)));
            System.Diagnostics.Debug.WriteLine(message, "Client (password)");
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
            clearBuf(recv);
            cb = ns.Read(recv, 0, recv.Length);
            message = Encoding.ASCII.GetString(recv);
            System.Diagnostics.Debug.WriteLine(message, "Server");
            if (message.StartsWith("535"))
                throw new Exception("Authentication unsuccessful");
            if (!message.StartsWith("2"))
                throw new Exception(string.Format("Unexpected reply to password:\n{0}", message));
    
            message = string.Format("MAIL FROM: <{0}>\r\n", FromEmail);
            System.Diagnostics.Debug.WriteLine(message, "Client");
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
            clearBuf(recv);
            cb = ns.Read(recv, 0, recv.Length);
            message = Encoding.ASCII.GetString(recv);
            System.Diagnostics.Debug.WriteLine(message, "Server");
            if (!message.StartsWith("250"))
                throw new Exception(string.Format("Unexpected reply to MAIL FROM:\n{0}", message));
    
            message = string.Format("RCPT TO: <{0}>\r\n", ToEmail);
            System.Diagnostics.Debug.WriteLine(message, "Client");
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
            clearBuf(recv);
            cb = ns.Read(recv, 0, recv.Length);
            message = Encoding.ASCII.GetString(recv);
            System.Diagnostics.Debug.WriteLine(message, "Server");
            if (!message.StartsWith("250"))
                throw new Exception(string.Format("Unexpected reply to RCPT TO:\n{0}", message));
    
            message = "DATA\r\n";
            System.Diagnostics.Debug.WriteLine(message, "Client");
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
            clearBuf(recv);
            cb = ns.Read(recv, 0, recv.Length);
            message = Encoding.ASCII.GetString(recv);
            System.Diagnostics.Debug.WriteLine(message, "Server");
            if (!message.StartsWith("354"))
                throw new Exception(string.Format("Unexpected reply to DATA:\n{0}", message));
    
            data = MIMEMessage;
            ns.Write(data, 0, data.Length);
            message = "\r\n.\r\n";
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
    
            clearBuf(recv);
            cb = ns.Read(recv, 0, recv.Length);
            message = Encoding.ASCII.GetString(recv);
            System.Diagnostics.Debug.WriteLine(message, "Server");
            if (!message.StartsWith("250"))
                throw new Exception(string.Format("Unexpected reply to end of data marker (\\r\\n.\\r\\n):\n{0}", message));
    
            message = "QUIT\r\n";
            System.Diagnostics.Debug.WriteLine(message, "Client");
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
    
        }
        catch (Exception ex)
        {
            throw new Exception(string.Format("SMTP Communication Error: {0}", message), ex);
        }
        finally
        {
            if (null != smtp) smtp.Close();
        }
    }
    #endregion
    
    #region BASE SendToOpenRelay
    /// <summary>
    /// Sends the message via a socket connection to an SMTP relay host. 
    /// </summary>
    /// <param name="hostname">Friendly-name or IP address of SMTP relay host</param>
    /// <param name="port">Port on which to connect to SMTP relay host</param>
    public void SendToOpenRelay(string HostName, int Port, string FromEmail, string ToEmail, byte[] MIMEMessage)
    {
    
        TcpClient smtp;
        NetworkStream ns;
        int cb;
        byte[] recv = new byte[256];
        byte[] data;
        string message;
    
        try
        {
            smtp = new TcpClient(HostName, Port);
            ns = smtp.GetStream();
            message = "HELO\r\n";
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
            cb = ns.Read(recv, 0, recv.Length);
            System.Diagnostics.Debug.WriteLine(Encoding.ASCII.GetString(recv), "Server");
        }
        catch(Exception ex)
        {
            throw new Exception(string.Format("Unable to establish SMTP session with {0}:{1}", HostName, Port), ex);
        }
    
        try
        {
            message = string.Format("MAIL FROM: <{0}>\r\n", FromEmail);
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
            cb = ns.Read(recv, 0, recv.Length);
            if (!Convert.ToString(recv).StartsWith("501"))
                throw new Exception("Malformed sender address");
            if (!Convert.ToString(recv).StartsWith("250"))
                throw new Exception(string.Format("SMTP host responded incorrectly to MAIL FROM:, response was:\n{0}", Convert.ToString(recv)));
            message = string.Format("RCPT TO: <{0}>\r\n", ToEmail);
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
            cb = ns.Read(recv, 0, recv.Length);
            if (Convert.ToString(recv).StartsWith("501"))
                throw new Exception("Malformed recipient address");
            if (!Convert.ToString(recv).StartsWith("250"))
                throw new Exception(string.Format("SMTP host responded incorrectly to RCPT TO:, response was:\n{0}", Convert.ToString(recv)));
            message = "DATA\r\n";
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
            cb = ns.Read(recv, 0, recv.Length);
            if (!Convert.ToString(recv).StartsWith("354"))
                throw new Exception(string.Format("SMTP host responded incorrectly to DATA, response was:\n{0}", Convert.ToString(recv)));
            data = MIMEMessage;
            ns.Write(data, 0, data.Length);
            message = "\r\n.\r\n";
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
            cb = ns.Read(recv, 0, recv.Length);
            message = "QUIT\r\n";
            data = Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
        }
        catch (Exception ex)
        {
    
    
    
                throw new Exception(string.Format("SMTP Communication Error: {0}", message), ex);
            }
            finally
            {
                if (null != smtp) smtp.Close();
            }
        }
        #endregion
    
        #region clearBuf
        private void clearBuf(byte[] buf)
        {
            for (int i = 0; i < buf.Length; i++) buf[i] = 0;
        }
        #endregion
    
        #endregion
    
    }
    

    }

答案 1 :(得分:1)

使用线程来执行异步IO是不必要的(大部分时间),并且会导致程序无法正常扩展,因为您现在可能会注意到这一点。

首先,我不确定你为什么要使用线程,如果你最终必须在共同对象上lock,这通常是代码气味的标志,我建议你再考虑一下。 / p>

一个好主意是将SmtpClient.SendMailAsync与C#5的async-await功能组合使用:

public async Task SendSmtpMailAsync()
{
    fullHtml = (fullHtml == null ? ViewToString("~/View/Shared/_DocumentTemplate.cshtml", controllerContext, new emailModel { body = body, subject = subject }) : fullHtml);
    messageMail.Body = fullHtml;
    System.Net.Mail.SmtpClient smtp = smtpClient ?? new SmtpClient();
    await smtp.SendMailAsync(messageMail);
}

您可以在第一段代码中使用它,如下所示:

using (System.Net.Mail.SmtpClient smtp = new System.Net.Mail.SmtpClient())
{
    await Utility.SendTemplatedMailAsync(model.email, "noreply@fakedomain.com", "New Jobs!", fullHtml: fullHtml, checkUnsubscribed: false, smtpClient: smtp);
}

// Note i removed the lock from this piece of code. If you have to execute multiple async methods concurrently (using Task.WhenAll), then maybe it should be re-added 
emailCounter++;
writer.WriteLine(string.Format("sent and smtp client closed: {0} -- email number: {1}", DateTime.UtcNow.ToString("dd/MM/yyyy hh:mm:ss.fff tt"), emailCounter.ToString()));

writer.Flush();
if (emailCounter == numberOfGroupedMatches) 
{
    writer.Close();
    System.IO.File.Move(Server.MapPath("~/Temp/") + "emailLog.txt", Server.MapPath("~/Temp/") + "emailLog-Finished.txt");
}