Sending large volumes of e-mails raises a number of performance and security considerations, and they often contradict each other. To name a few:
Ways to improve performance | Limitations imposed by many SMTP servers |
---|---|
It's much faster to send several e-mails through a single SMTP session than to send one e-mail per session. | You can't send unlimited number of e-mails per session. |
It's usually faster to send several e-mails simultaneously in separate threads (and each thread can send multiple e-mails per session). | You can't make multiple simultaneous connections from a single IP address. |
Higher sending rate allows for quicker delivery of bulk mail. | The maximum allowed number of e-mails which can be accepted in a hour or day from a single IP address is strictly limited. |
If you face such issues, you'll need to achieve balance between the performance and the security limitations of your e-mail server. For instance, send no more than 10 e-mails per session and use up to 3 simultaneous connections if these are the limits, and so on. MailBee.NET provides the extensive settings for that.
Also, you can send through multiple SMTP servers for better throughput and reliability, or use a mail queue like MailBee.NET Queue to deliver your e-mails.
This guide demonstrates different techniques of improving performance, reducing memory usage, error handling and dealing with other issues related to bulk mailing.
If you compose a message, then call Smtp.Send, then compose another and call Smtp.Send again, MailBee.NET will establish the connection twice. For better performance, you can establish the connection manually and send both e-mails within a single SMTP session:
Smtp mailer = new Smtp(); // Use SMTP relay with authentication. mailer.SmtpServers.Add("smtp.provider.com", "joe@company.com", "secret"); // Connect and authenticate. mailer.Connect(); mailer.Hello(); mailer.Login(); // Set up 1st simple e-mail. mailer.Message.From.Email = "joe@company.com"; mailer.Message.To = new EmailAddressCollection("jane@domain.com"); mailer.Message.Subject = "Message for Jane"; mailer.Message.BodyPlainText = "This is a test message for Jane."; // Send 1st e-mail. mailer.Send(); // Set up 2nd simple e-mail from the same sender joe@company.com. mailer.Message.To = new EmailAddressCollection("carl@example.com"); mailer.Message.Subject = "Message for Carl"; mailer.Message.BodyPlainText = "This is a test message for Carl."; // Send 2nd e-mail. mailer.Send(); // Since we connected manually, we need to disconnect manually either. mailer.Disconnect();
Dim mailer As New Smtp() ' Use SMTP relay with authentication. mailer.SmtpServers.Add("smtp.provider.com", "joe@company.com", "secret") ' Connect and authenticate. mailer.Connect() mailer.Hello() mailer.Login() ' Set up 1st simple e-mail. mailer.Message.From.Email = "joe@company.com" mailer.Message.To = New EmailAddressCollection("jane@domain.com") mailer.Message.Subject = "Message for Jane" mailer.Message.BodyPlainText = "This is a test message for Jane." ' Send 1st e-mail. mailer.Send() ' Set up 2nd simple e-mail from the same sender joe@company.com. mailer.Message.To = New EmailAddressCollection("carl@example.com") mailer.Message.Subject = "Message for Carl" mailer.Message.BodyPlainText = "This is a test message for Carl." ' Send 2nd e-mail. mailer.Send() ' Since we connected manually, we need to disconnect manually either. mailer.Disconnect()
We assume mailer
is an Smtp object instance. In addition to that, for all the samples in this guide we assume MailBee, MailBee.Mime, and MailBee.SmtpMail namespaces are imported and the license key is set. See Import namespaces and set license key topic for details.
As you can see, we do not set "From:" address for the second e-mail in the series. Smtp object does not destroy the message upon sending so you can reuse its contents for subsequent messages. If you, however, need to reset it, call Smtp.ResetMessage method.
The sample in this topic adds the following functionality to the previous sample:
Note that to compile this sample you also need to import MailBee.Security namespace. No additional license keys, though.
Smtp mailer = new Smtp(); // Let's enable logging SMTP session into a file so that you can then // examine which commands MailBee.NET sent to the server and how many // connections it has made. mailer.Log.Enabled = true; mailer.Log.Filename = @"C:\Temp\log.txt"; mailer.Log.Clear(); // Use SMTP relay with authentication. SmtpServer server = mailer.SmtpServers.Add("smtp.company.com", "sender", "secret"); // Use STARTTLS (SSL over regular SMTP port) when it's supported. server.SslMode = SslStartupMode.UseStartTlsIfSupported; // Or uncomment to use dedicated SSL connection over port 465. // server.SslMode = SslStartupMode.OnConnect; // server.Port = 465; // Connect and authenticate. mailer.Connect(); mailer.Hello(); mailer.Login(); // Set 1st e-mail (plain-text with attachment). mailer.Message.From = new EmailAddress("joe@company.com", "Joe T."); mailer.Message.To.Add("jane@domain.com", "Jane D."); mailer.Message.To.Add("mike@domain.com", "Mike R."); mailer.Message.Subject = "Message #1"; mailer.Message.BodyPlainText = "plain-text message"; mailer.Message.Attachments.Add(@"C:\Docs\document1.doc"); // Send 1st e-mail. mailer.Send(); // If you don't reset the message, you'd need to clear some properties // manually (for instance, clear Attachments collection). But if all // your e-mails contain the same set of attachments, you can optimize // the performance and don't reset the message to have the attachments // reused for every message being sent. mailer.ResetMessage(); // Set 2nd e-mail (HTML with attachment). mailer.Message.From.AsString = "Bill T. <bill@company.com>"; mailer.Message.To.AsString = "Susan C. <susan@domain.com"; mailer.Message.To.AddFromString("Gregory <greg@example.com>"); mailer.Message.Subject = "Message #2"; mailer.Message.BodyHtmlText = "<html>HTML message</html>"; mailer.Message.Attachments.Add(@"C:\Docs\document2.doc"); // Send 2nd e-mail. mailer.Send(); // Since we connected manually, we need to disconnect manually either. mailer.Disconnect();
Dim mailer As New Smtp() ' Let's enable logging SMTP session into a file so that you can then ' examine which commands MailBee.NET sent to the server and how many ' connections it has made. mailer.Log.Enabled = True mailer.Log.Filename = "C:\Temp\log.txt" mailer.Log.Clear() ' Use SMTP relay with authentication. Dim server As SmtpServer = mailer.SmtpServers.Add("smtp.company.com", "sender", "secret") ' Use STARTTLS (SSL over regular SMTP port) when it's supported. server.SslMode = SslStartupMode.UseStartTlsIfSupported ' Or uncomment to use dedicated SSL connection over port 465. ' server.SslMode = SslStartupMode.OnConnect ' server.Port = 465 ' Connect and authenticate. mailer.Connect() mailer.Hello() mailer.Login() ' Set 1st e-mail (plain-text with attachment). mailer.Message.From = New EmailAddress("joe@company.com", "Joe T.") mailer.Message.To.Add("jane@domain.com", "Jane D.") mailer.Message.To.Add("mike@domain.com", "Mike R.") mailer.Message.Subject = "Message #1" mailer.Message.BodyPlainText = "plain-text message" mailer.Message.Attachments.Add("C:\Docs\document1.doc") ' Send 1st e-mail. mailer.Send() ' If you don't reset the message, you'd need to clear some properties ' manually (for instance, clear Attachments collection). But if all ' your e-mails contain the same set of attachments, you can optimize ' the performance and don't reset the message to have the attachments ' reused for every message being sent. mailer.ResetMessage() ' Set 2nd e-mail (HTML with attachment). mailer.Message.From.AsString = "Bill T. <bill@company.com>" mailer.Message.To.AsString = "Susan C. <susan@domain.com" mailer.Message.To.AddFromString("Gregory <greg@example.com>") mailer.Message.Subject = "Message #2" mailer.Message.BodyHtmlText = "<html>HTML message</html>" mailer.Message.Attachments.Add("C:\Docs\document2.doc") ' Send 2nd e-mail. mailer.Send() ' Since we connected manually, we need to disconnect manually either. mailer.Disconnect()
To send large volumes of e-mails you can use a number of methods listed in Introduction. The sample below uses them all, including multi-threading. All parameters of the SMTP servers being used are set for optimal performance. If the server implies some restrictions on the sending process (such as maximum number of simultaneous IP connections from a single IP address), you can adjust the corresponding parameters of SmtpServerobjects which represent the relay servers being used.
Unlike previous samples, this sample does not explicitly call Smtp.Connect method. Instead, it uses the concept of jobs (see Bulk mail, explained topic for details).
Smtp.SendJobs method is smart enough to automatically connect to the server, send multiple number of e-mails over this connection, automatically disconnect if the maximum number of message per session reached, reconnect, and so on. It can also distribute the load between multiple servers, use fail-over servers, and so on.
By the way, the regular Smtp.Send method is also capable of using fail-over servers. See Send through main SMTP relay server or through fail-over SMTP relay server if main server is down topic on this.
The sample generates and sends 100 e-mails. Note that we do not generate all the e-mails at once. Instead, we generate only 20 e-mails as initial portion (so that up to 20 e-mails could be sent simultaneously). If we generated 100 e-mails at once, they would have occupied pretty much memory (imagine if you had 10.000 e-mails to be sent). Instead, this sample shows how to "feed" the jobs list while Smtp.SendJobs method is running. In other words, you can add new jobs when some jobs are already being processed.
However, when creating a mail merge job, you do not need to worry about such optimizations of memory use. MailBee.NET creates an individual e-mail in a mail merge series and keeps it in memory only for a short moment while this e-mail is being sent.
Last note is on multi-threading. This sample enables logging of all the activities into a file, and the extended format of the log file is used to make it easier to track individual sending threads.
Also, you may ask "where is the synchronization of access to the collection of pending jobs?". Such synchronization is needed to make sure no race condition occurs in case when you modify the collection by adding a new job in the main thread while the worker thread of MailBee.NET modifies the same collection by extracting the existing job at the very same moment. So, how to get things synchronized?
The answer: Smtp.AddJob and Smtp.RetryFailedJobs methods are automatically synchronized, for the rest you can use Smtp.JobsSyncRoot property.
Here we go:
using System; using MailBee; using MailBee.Mime; using MailBee.SmtpMail; class Sample { static int i; // Creates an e-mail and adds it into the list of pending jobs. // For brevity, all e-mails are very simple. In real-world apps, // you can make e-mails as complex as you need. static void AddEmailAsJob(Smtp mailer) { string s = i.ToString("000"); // 25 -> "025". mailer.Message.From.AsString = "joe@domain.com"; // For every even i (2, 4, ..., 100) we'll generate invalid e-mail // address with missing "@" character to demonstrate what happens // in case of an error. string at = (i % 2) == 0 ? "" : "@"; mailer.Message.To.AsString = "user_" + s + at + "company.com"; mailer.Message.Subject = "Message #" + s; mailer.Message.BodyPlainText = "Text of message #" + s; mailer.AddJob(s, null, null); } // In case if less than 100 e-mails have been made to the moment, // create one more. To free memory, finished job won't be preserved. static void OnJobReady(object sender, SmtpFinishingJobEventArgs e) { Smtp mailer = (Smtp)sender; if (i <= 100) { AddEmailAsJob(mailer); i++; } if (e.IsSuccessful) { Console.WriteLine("Sent to " + e.Job.Recipients.ToString()); } else { Console.WriteLine(e.Job.ErrorReason.Message); } Console.WriteLine(); // We don't want the e-mail be kept in memory as we need to create // subsequent e-mails, and the memory resource is limited. e.KeepIt = false; } static void Main(string[] args) { Smtp mailer = new Smtp(); mailer.Log.Enabled = true; mailer.Log.Filename = @"C:\Temp\log.txt"; mailer.Log.Format = LogFormatOptions.AddContextInfo; mailer.Log.Clear(); // Use SMTP relays with authentication. Both relay have the same // priority and the load will be distributed equally between them. SmtpServer server1 = mailer.SmtpServers.Add("smtp1.domain.com", "joe", "secret"); SmtpServer server2 = mailer.SmtpServers.Add("smtp2.domain.com", "joe", "secret"); // Configure 1st server for optimal performance. Actually, // these are the default values so we list them here for // demonstration purposes only. server1.PauseInterval = 0; server1.MaxConnectionCount = -1; server1.MaxSendPerSessionCount = -1; // Let's use up to 20 threads. mailer.MaxThreadCount = 20; // Feed Smtp object with 20 e-mails to be sent. // This is the minimum count to feed all 20 threads. // However, it's not a requirement, you can use any // combinations of numbers of threads and jobs. for (i = 1; i <= 20; i++) { AddEmailAsJob(mailer); } // Will use it to add more jobs when SendJobs is working. mailer.FinishingJob += new SmtpFinishingJobEventHandler(OnJobReady); // This will send 100 e-mails in spite of we initially added only 20. mailer.SendJobs(); } }
Imports System Imports MailBee Imports MailBee.Mime Imports MailBee.SmtpMail Module Module1 Dim i As Integer ' Creates an e-mail and adds it into the list of pending jobs. ' For brevity, all e-mails are very simple. In real-world apps, ' you can make e-mails as complex as you need. Sub AddEmailAsJob(ByVal mailer As Smtp) Dim s As String = i.ToString("000") ' 25 -> "025". mailer.Message.From.AsString = "joe@domain.com" ' For every even i (2, 4, ..., 100) we'll generate invalid e-mail ' address with missing "@" character to demonstrate what happens ' in case of an error. Dim at As String = IIf(i Mod 2 = 0, "", "@") mailer.Message.To.AsString = "user_" & s & at & "company.com" mailer.Message.Subject = "Message #" & s mailer.Message.BodyPlainText = "Text of message #" & s mailer.AddJob(s, Nothing, Nothing) End Sub ' In case if less than 100 e-mails have been made to the moment, ' create one more. To free memory, finished job won't be preserved. Sub OnJobReady(ByVal sender As Object, _ ByVal e As SmtpFinishingJobEventArgs) Dim mailer As Smtp = CType(sender, Smtp) If i <= 100 Then AddEmailAsJob(mailer) i += 1 End If If e.IsSuccessful Then Console.WriteLine(("Sent to " & e.Job.Recipients.ToString())) Else Console.WriteLine(e.Job.ErrorReason.Message) End If Console.WriteLine() ' We don't want the e-mail be kept in memory as we need to create ' subsequent e-mails, and the memory resource is limited. e.KeepIt = False End Sub Sub Main() Dim mailer As New Smtp() mailer.Log.Enabled = True mailer.Log.Filename = "C:\Temp\log.txt" mailer.Log.Format = LogFormatOptions.AddContextInfo mailer.Log.Clear() ' Use SMTP relays with authentication. Both relay have the same ' priority and the load will be distributed equally between them. Dim server1 As SmtpServer = _ mailer.SmtpServers.Add("smtp1.domain.com", "joe", "secret") Dim server2 As SmtpServer = _ mailer.SmtpServers.Add("smtp2.domain.com", "joe", "secret") ' Configure 1st server for optimal performance. Actually, ' these are the default values so we list them here for ' demonstration purposes only. server1.PauseInterval = 0 server1.MaxConnectionCount = -1 server1.MaxSendPerSessionCount = -1 ' Let's use up to 20 threads. mailer.MaxThreadCount = 20 ' Feed Smtp object with 20 e-mails to be sent. ' This is the minimum count to feed all 20 threads. ' However, it's not a requirement, you can use any ' combinations of numbers of threads and jobs. For Module1.i = 1 To 20 AddEmailAsJob(mailer) Next i ' Will use it to add more jobs when SendJobs is working. AddHandler mailer.FinishingJob, AddressOf OnJobReady ' This will send 100 e-mails in spite of we initially added only 20. mailer.SendJobs() End Sub End Module
To learn how you can increase performance further, refer to Performance optimization topic.
Note that you cannot use some methods of Smtp class while in multi-threaded mode. See Smtp.IsSmtpContext property documentation for details.
You can assign different priorities to your SMTP relay servers so that some servers would be used only if primary servers are not available. In this sample, we use 1 primary and 1 backup server. You can use as many as you need. This is compatible with bulk mail too and you can still have multiple servers of the same priority to distribute the load amongst them.
So that you can have, let's say, a server farm of 5 primary servers of the top priority (priority=0), plus 10 secondary slow servers (priority=1) used when the primary servers are down, and a low-priority server or your ISP (priority=2) which will be used when the secondary servers go down too.
This sample sends a single e-mail through a primary SMTP relay server. If it fails, the secondary server is tried, and warning is saved into the log file:
Smtp mailer = new Smtp(); // You can see in the log what happens if the primary server // is down and how the second server is being tried then. mailer.Log.Enabled = true; mailer.Log.Filename = @"C:\Temp\log.txt"; mailer.Log.Clear(); // Use SMTP relays with authentication. SmtpServer server1 = mailer.SmtpServers.Add("smtp1.domain.com", "j.doe", "secret"); SmtpServer server2 = mailer.SmtpServers.Add("smtp2.domain.com", "j.doe", "secret"); // Simulate a problem with the primary server by giving it // incorrect host name. server1.Name = "nonsense"; // Lower the priority of 2nd server. MailBee.NET will use it // only as a fail-over server when 1st server is down. server2.Priority = 1; mailer.Message.From.AsString = "John Doe <j.doe@domain.com>"; mailer.Message.To.AsString = "Jane Doe <jane@example.com>"; mailer.Message.Subject = "Important message for Jane"; mailer.Message.BodyPlainText = "Meet me tomorrow"; // Send through "nonsense". If it's down (and it certainly is), // send through smtp2.domain.com then. mailer.Send();
Dim mailer As New Smtp() ' You can see in the log what happens if the primary server ' is down and how the second server is being tried then. mailer.Log.Enabled = True mailer.Log.Filename = "C:\Temp\log.txt" mailer.Log.Clear() ' Use SMTP relays with authentication. Dim server1 As SmtpServer = _ mailer.SmtpServers.Add("smtp1.domain.com", "j.doe", "secret") Dim server2 As SmtpServer = _ mailer.SmtpServers.Add("smtp2.domain.com", "j.doe", "secret") ' Simulate a problem with the primary server by giving it ' incorrect host name. server1.Name = "nonsense" ' Lower the priority of 2nd server. MailBee.NET will use it ' only as a fail-over server when 1st server is down. server2.Priority = 1 mailer.Message.From.AsString = "John Doe <j.doe@domain.com>" mailer.Message.To.AsString = "Jane Doe <jane@example.com>" mailer.Message.Subject = "Important message for Jane" mailer.Message.BodyPlainText = "Meet me tomorrow" ' Send through "nonsense". If it's down (and it certainly is), ' send through smtp2.domain.com then. mailer.Send()
If you would also like to monitor such non-critical failures with events, subscribe to Smtp.ErrorOccurred event. It occurs in many cases, including situations when MailBee.NET needs to switch to the fail-over server.
The topics above deal only with SMTP relay servers. However, you can mix the use of SMTP relay servers with direct send. To learn more on direct send, refer to Direct send without SMTP relay server guide.
For instance, let's assume you have:
In this case, bulk mail operations will distribute the load between 2 SMTP relays of top priority, downgrade to direct send and distribute the load of making DNS MX lookup queries between 4 DNS servers, and if they fail too, use the lowest priority SMTP relay server.
Thus, direct send can be both low or high priority in comparison to SMTP relay send, and you can build very complex and powerful fail-over schemas with this feature.
This sample sends a single e-mail through a primary SMTP relay server. If it fails, the direct send is tried, and warning is saved into the log file:
Smtp mailer = new Smtp(); // You can see in the log what happens if the primary server // is down and how direct send via DNS MX lookup is tried then. mailer.Log.Enabled = true; mailer.Log.Filename = @"C:\Temp\log.txt"; mailer.Log.Clear(); // Use SMTP relay with authentication. SmtpServer relay = mailer.SmtpServers.Add("smtp.domain.com", "j.doe", "secret"); if (!mailer.DnsServers.Autodetect()) { // Add DNS server manually if couldn't autodetect them // from the system. mailer.DnsServers.Add("192.168.0.1"); } // We do not need to manually lower the priority for DNS servers // to make them the backup method of sending because if SMTP and // DNS servers have the same priority value (zero by default), // SMTP server is always selected. However, in case if you need // to change DNS server priority, that's how you can do this. foreach (DnsServer dns in mailer.DnsServers) { dns.Priority = 1; } // Simulate a problem with the primary server by giving it // incorrect host name. relay.Name = "nonsense"; mailer.Message.From.AsString = "John Doe <j.doe@domain.com>"; mailer.Message.To.AsString = "Jane Doe <jane@example.com>"; mailer.Message.Subject = "Important message for Jane"; mailer.Message.BodyPlainText = "Meet me tomorrow"; // Send through "nonsense". If it's down (and it certainly is), // direct send will be used. mailer.Send();
Dim mailer As New Smtp() ' You can see in the log what happens if the primary server ' is down and how direct send via DNS MX lookup is tried then. mailer.Log.Enabled = True mailer.Log.Filename = "C:\Temp\log.txt" mailer.Log.Clear() ' Use SMTP relay with authentication. Dim relay As SmtpServer = mailer.SmtpServers.Add( _ "smtp.domain.com", "j.doe", "secret") If Not mailer.DnsServers.Autodetect() Then ' Add DNS server manually if couldn't autodetect them ' from the system. mailer.DnsServers.Add("192.168.0.1") End If ' We do not need to manually lower the priority for DNS servers ' to make them the backup method of sending because if SMTP and ' DNS servers have the same priority value (zero by default), ' SMTP server is always selected. However, in case if you need ' to change DNS server priority, that's how you can do this. Dim dns As DnsServer For Each dns In mailer.DnsServers dns.Priority = 1 Next dns ' Simulate a problem with the primary server by giving it ' incorrect host name. relay.Name = "nonsense" mailer.Message.From.AsString = "John Doe <j.doe@domain.com>" mailer.Message.To.AsString = "Jane Doe <jane@example.com>" mailer.Message.Subject = "Important message for Jane" mailer.Message.BodyPlainText = "Meet me tomorrow" ' Send through "nonsense". If it's down (and it certainly is), ' direct send will be used. mailer.Send()
Note that you'll also need to import MailBee.DnsMX namespace to compile this sample.
The ultimate answer on the question "how to quickly and reliably send e-mails?" is using a mail queue. If your e-mail server is IIS SMTP or compatible, you can submit e-mails directly to its pickup folder bypassing the SMTP protocol at all. No need to make network connections, pass through firewalls and so on.
If you do not have a local SMTP server or it does not support the pickup folder service, you can use MailBee.NET Queue instead. In this case, you'll still need to have an SMTP relay server (MailBee.NET Queue can use direct send but a dedicated SMTP relay is much more preferable). Also, the firewall must allow connections from MailBee.NET Queue to the relay server. But the major benefit is that your application now off-loads the task of delivering e-mails to MailBee.NET Queue can can focus on other tasks.
MailBee.NET Queue can send with multi-threading for better performance, allows you to limit the throughput if your SMTP server requires this (for instance, limit the maximum number of e-mails being sent per minute). By the way, MailBee.NET Queue is shipped with the source code so you can use it as an advanced sample of building a high-performance e-mail sending application with MailBee.NET SMTP component.
You can also use MailBee.NET Queue as a fail-over method of sending e-mail in your application. For instance, if the relay server is down at the current moment, you can submit the message to the MailBee.NET Queue and proceed with other tasks. When the server is up again, MailBee.NET Queue will deliver the message.
Several Visual Studio sample projects demonstrating how to submit e-mails to MailBee.NET Queue (including mail merge) are shipped with MailBee.NET Queue itself. Once installed MailBee.NET Queue, you can find them in Samples section of Start /Programs / MailBee.NET Queue menu.
This sample below sends a e-mail to the SMTP relay server and switches to MailBee.NET Queue in case if the server is down.
Note that the sample does not switch to MailBee.NET Queue in case of some errors. For instance, if the server replied that the specified recipient's e-mail address is invalid, the server isn't down, it just that the message is bad and MailBee.NET Queue won't deliver it either. In such case when the problem is with the message itself, the sample throws an exception:
Smtp mailer = new Smtp(); // You can see in the log the difference between "the server is down" // and "the server specially rejected the message". mailer.Log.Enabled = true; mailer.Log.Filename = "C:\\Temp\\log.txt"; mailer.Log.Clear(); // Use SMTP relay with authentication. SmtpServer relay = mailer.SmtpServers.Add("smtp.domain.com", "j.doe", "secret"); // Simulate a problem with the relay server by giving it // incorrect host name. Comment this to use the real server. relay.Name = "nonsense"; // Note missing @ in To address. We demonstrate what happens // if the server is OK but the e-mail isn't. mailer.Message.To.AsString = "Jane Doe <janeexample.com>"; // Set other fields of the e-mail. mailer.Message.From.AsString = "John Doe <j.doe@domain.com>"; mailer.Message.Subject = "Important message for Jane"; mailer.Message.BodyPlainText = "Meet me tomorrow"; // Send through "nonsense" if you haven't commented it out. // If the relay server is down, will submit to MailBee.NET Queue. bool useQueue = false; try { mailer.Send(); } catch (MailBeeSmtpSendNegativeResponseException e) { // If the server replied with a negative reply during sending // e-mail, this probably means the message is incorrect and we // should not retry with MailBee.NET Queue. // Check if the error is with the server, not with the message. // In theory, permanent error can also be the problem of the // server but if the server is configured correctly and we use // correct credentials for authentication, it should generate // permanent errors only for bad messages. if (e.IsTransientError) { // Temporary error, makes sense to try again later with // MailBee.NET Queue. useQueue = true; } else { // The message is probably bad, doesn't make sense to try // again with MailBee.NET Queue. throw; } } catch (MailBeeNetworkException) { // The server is down or another error occurred but it does not // look to be related to the fact that the message is bad. useQueue = true; } // If could not send normally, submit the message as .EML file // into the pickup folder of MailBee.NET Queue for later delivery. // Or, we could have written into IIS SMTP Pickup folder. if (useQueue) { mailer.SubmitToPickupFolder( "C:\\MailBeeNetQueue Files\\Pickup", false); }
Dim mailer As New Smtp() ' You can see in the log the difference between "the server is down" ' and "the server specially rejected the message". mailer.Log.Enabled = True mailer.Log.Filename = "C:\Temp\log.txt" mailer.Log.Clear() ' Use SMTP relay with authentication. Dim relay As SmtpServer = _ mailer.SmtpServers.Add("smtp.domain.com", "j.doe", "secret") ' Simulate a problem with the relay server by giving it ' incorrect host name. Comment this to use the real server. relay.Name = "nonsense" ' Note missing @ in To address. We demonstrate what happens ' if the server is OK but the e-mail isn't. mailer.Message.To.AsString = "Jane Doe <janeexample.com>" ' Set other fields of the e-mail. mailer.Message.From.AsString = "John Doe <j.doe@domain.com>" mailer.Message.Subject = "Important message for Jane" mailer.Message.BodyPlainText = "Meet me tomorrow" ' Send through "nonsense" if you haven't commented it out. ' If the relay server is down, will submit to MailBee.NET Queue. Dim useQueue As Boolean = False Try mailer.Send() Catch e As MailBeeSmtpSendNegativeResponseException ' If the server replied with a negative reply during sending ' e-mail, this probably means the message is incorrect and we ' should not retry with MailBee.NET Queue. ' Check if the error is with the server, not with the message. ' In theory, permanent error can also be the problem of the ' server but if the server is configured correctly and we use ' correct credentials for authentication, it should generate ' permanent errors only for bad messages. If e.IsTransientError Then ' Temporary error, makes sense to try again later with ' MailBee.NET Queue. useQueue = True Else ' The message is probably bad, doesn't make sense to try ' again with MailBee.NET Queue. Throw End If Catch e As MailBeeNetworkException ' The server is down or another error occurred but it does not ' look to be related to the fact that the message is bad. useQueue = True End Try ' If could not send normally, submit the message as .EML file ' into the pickup folder of MailBee.NET Queue for later delivery. ' Or, we could have written into IIS SMTP Pickup folder. If useQueue Then mailer.SubmitToPickupFolder( _ "C:\MailBeeNetQueue Files\Pickup", False) End If
In the sample above, you can also add handling of MailBeeSmtpMessageNotAllowedException if it occurs. It's usually thrown when the SMTP server won't accept the message because it's too big. What to do in this case, it's up to you to decide:
In most cases, however, you'll probably use a mail queue like MailBee.NET Queue on its own, without using hybrid schemes (send via SMTP relay and fall back to MailBee.NET Queue) in a single application. Using a mail queue of any kind is especially effective if you send lots of e-mails, including mail merge.
The example of using MailBee.NET Queue with mail merge can be found at Submit e-mails generated with mail merge to MailBee.NET Queue topic.
You can limit the maximum number of e-mails being sent per second, the maximum simultaneous number of connections and so on if the SMTP server you're dealing with implies certain restrictions on these parameters. For instance, many servers do not allow more than 5 simultaneous connections from a single IP address.
You can set the following parameters:
For instance, if SmtpServer.MaxConnectionCount is 1, no simultaneous connections with this server will be made, even if Smtp.MaxThreadCount is not 1.
Another example. If SmtpServer.MaxSendPerSessionCount is 5 and SmtpServer.PauseInterval is 1000, MailBee.NET will send no more than 5 e-mails per second, and each new session will be established after 1 second since completion of the previous session.
Note that if you're using multiple relay servers (your Smtp.SmtpServers collection contains several SmtpServer objects), you can set sending limits individually for each server.
The code snippet below show how to configure these parameters for a relay server. We assume your have two relay servers at your disposal: the first one is your corporate server which does not imply any restrictions while another one is not intended for bulk mailing (such as your ISP's server), and used only as a backup server in case of any issues with the primary server:
Smtp mailer = new Smtp(); // Primary corp relay with no restrictions. Send as fast as we can. SmtpServer corpRelay = mailer.SmtpServers.Add( "smtp.company.com", "user", "password"); // Fail-over ISP's relay with some restrictions. SmtpServer ispRelay = mailer.SmtpServers.Add( "mail.provider.com", "joe@company.com", "secret"); // Decrease the priority from zero (highest) to 1. The ISP's relay // will be used only when the primary corporate relay is not accessible. ispRelay.Priority = 1; // Let's imagine the relay server of your company's ISP has these limitations: // only a single connection per IP address allowed (no concurrent connections), // only a single e-mail per an SMTP connection can be sent, and no more than 10 // e-mails can be sent per minute. We can fit into this limitation only when // sending single e-mails not more often than every 6 seconds. ispRelay.MaxConnectionCount = 1; ispRelay.MaxSendPerSessionCount = 1; ispRelay.PauseInterval = 6000;
Dim mailer As New Smtp() ' Primary corp relay with no restrictions. Send as fast as we can. Dim corpRelay As SmtpServer = mailer.SmtpServers.Add( _ "smtp.company.com", "user", "password") ' Fail-over ISP's relay with some restrictions. Dim ispRelay As SmtpServer = mailer.SmtpServers.Add( _ "mail.provider.com", "joe@company.com", "secret") ' Decrease the priority from zero (highest) to 1. The ISP's relay ' will be used only when the primary corporate relay is not accessible. ispRelay.Priority = 1 ' Let's imagine the relay server of your company's ISP has these limitations: ' only a single connection per IP address allowed (no concurrent connections), ' only a single e-mail per an SMTP connection can be sent, and no more than 10 ' e-mails can be sent per minute. We can fit into this limitation only when ' sending single e-mails not more often than every 6 seconds. ispRelay.MaxConnectionCount = 1 ispRelay.MaxSendPerSessionCount = 1 ispRelay.PauseInterval = 6000
MailBee.NET Queue uses these parameters to implement throttling. You can examine its source code (it's included with the product) to learn how they are used in a real-world application. Try to experiment with different values of these parameters in MailBee.NET Queue to see how they influence the overall sending performance. This will help you find which parameters are acceptable by your server (if you don't know them in the first place). Since MailBee.NET Queue uses the same Smtp class like your own app, you can then use the parameter values you previously determined with MailBee.NET Queue, in your app.
A few hints on how you can improve performance, send e-mails faster and consume less resources. We assume you have full control over the SMTP relay server as well:
If everything is set up right, you can easily send more than a hundred of e-mails per second with MailBee.NET and a powerful e-mail server like MS Exchange. This results in more than a million of small e-mails per 3 hours.
Copyright © 2006-2024 AfterLogic Corporation. All rights reserved.