It is a common scenario for any corporate environment to periodically send large quantities of e-mails.
However, as important as sending the mail - is knowing who didn't receive the mail. It is therefor vital to catch all e-mails that could not be delivered to a recipient. People from Sales love this sort of detail!
Luckily the System.Net.Mail namespace offers us the SmtpFailedRecipientException class which is ideal for this scenario.
The example below is a nearly complete blueprint for a mailingtool and demonstrates the use of this class. It will:
- Send an e-mail to multiple recipients, one-by-one
- Catch the ones that fail
- Try to re-send failed deliveries that can be handled (i.e. MailboxBusy)
- Leave you with a list of recipients that failed for unhandled reasons
static void Main(string[] args)
{
// Generate a list of recipients:
List<string> ListOfRecipients = new List<string>();
ListOfRecipients.Add("johndoe@microsoft.com");
ListOfRecipients.Add("johndoe@nonexistingdomain.com"); // this wil fail, logically
// Generate a (empty) container for recipients that will fail ("recipient", "errormessage"):
Dictionary<string, string> ListOfFailedRecipients = new Dictionary<string, string>();
// Make up the variables for the e-mail (we'll re-use them in the exception handling):
SmtpClient mailServer = new SmtpClient("smtp.yourisp.com");
string mailSender = "ben@contoso.com";
string mailSubject = "This is the subject";
string mailBody = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit...";
foreach (string recipient in ListOfRecipients)
{
try
{
// Try to send an e-mail to each recipient:
mailServer.Send(mailSender, recipient, mailSubject, mailBody);
}
catch (SmtpFailedRecipientException ex)
{
// Sending failed. Let's determine *why* and see if we can do anything about it:
switch (ex.StatusCode)
{
case SmtpStatusCode.MailboxBusy:
// ... wait for a couple of seconds, then try again:
System.Threading.Thread.Sleep(3000);
mailServer.Send(mailSender, recipient, mailSubject, mailBody);
break;
default:
// ... in any other case, just give up and mark the recipient as 'failed':
ListOfFailedRecipients.Add(recipient, ex.Message);
break;
}
}
catch (Exception ex)
{
// Catch any other exception as a 'failed' delivery:
ListOfFailedRecipients.Add(recipient, ex.Message);
}
}
}
At the end we naturally could do something with the ListOfFailedRecipients, considering we now have both the recipient and a specific error message on "why" the delivery failed.
Additional exception handling
But we could be more effective by extending the switch statement with more specific SmtpStatusCode's. Here is the complete list of possible status goodies to play around with:
| Member name | Description |
| |
SystemStatus |
A system status or system Help reply. |
| |
HelpMessage |
A Help message was returned by the service. |
| |
ServiceReady |
The SMTP service is ready. |
| |
ServiceClosingTransmissionChannel |
The SMTP service is closing the transmission channel. |
| |
Ok |
The email was successfully sent to the SMTP service. |
| |
UserNotLocalWillForward |
The user mailbox is not located on the receiving server; the server forwards the e-mail. |
| |
CannotVerifyUserWillAttemptDelivery |
The specified user is not local, but the receiving SMTP service accepted the message and attempted to deliver it. This status code is defined in RFC 1123, which is available at http://www.ietf.org. |
| |
StartMailInput |
The SMTP service is ready to receive the e-mail content. |
| |
ServiceNotAvailable |
The SMTP service is not available; the server is closing the transmission channel. |
| |
MailboxBusy |
The destination mailbox is in use. |
| |
LocalErrorInProcessing |
The SMTP service cannot complete the request. This error can occur if the client's IP address cannot be resolved (that is, a reverse lookup failed). You can also receive this error if the client domain has been identified as an open relay or source for unsolicited e-mail (spam). For details, see RFC 2505, which is available at http://www.ietf.org. |
| |
InsufficientStorage |
The SMTP service does not have sufficient storage to complete the request. |
| |
ClientNotPermitted |
The client was not authenticated or is not allowed to send mail using the specified SMTP host. |
| |
CommandUnrecognized |
The SMTP service does not recognize the specified command. |
| |
SyntaxError |
The syntax used to specify a command or parameter is incorrect. |
| |
CommandNotImplemented |
The SMTP service does not implement the specified command. |
| |
BadCommandSequence |
The commands were sent in the incorrect sequence. |
| |
MustIssueStartTlsFirst |
The SMTP server is configured to accept only TLS connections, and the SMTP client is attempting to connect by using a non-TLS connection. The solution is for the user to set EnableSsl=true on the SMTP Client. |
| |
CommandParameterNotImplemented |
The SMTP service does not implement the specified command parameter. |
| |
MailboxUnavailable |
The destination mailbox was not found or could not be accessed. |
| |
UserNotLocalTryAlternatePath |
The user mailbox is not located on the receiving server. You should resend using the supplied address information. |
| |
ExceededStorageAllocation |
The message is too large to be stored in the destination mailbox. |
| |
MailboxNameNotAllowed |
The syntax used to specify the destination mailbox is incorrect. |
| |
TransactionFailed |
The transaction failed. |
| |
GeneralFailure |
The transaction could not occur. You receive this error when the specified SMTP host cannot be found. |
Do note that although the list seems exhaustive, the SmtpStatusCode is based on the reply from the SMTP server. So, if your SMTP server (or ISP) implements the SMTP RFC 2821 standard, which most do, then you're good to go.
When you want to handle SMTP server exceptions you can always use the more generic SmtpException() class. Like the SmtpFailedRecipient and SmtpFailedRecipients classes mentioned above it also contains the StatusCode property, but is not limited to handling only message deliveries that failed. It also handles all sorts of (SMTP) server and connection, resulting in the fact that you cannot always point an error from SmtpException() to a single recipient.
Update - RecipientNotFound
It should be noted that the SmtpFailedRecipientException() only works for local mail servers and recipients. Think Exchange, ActiveDirectory etc. If you are sending to an external mail system it cannot be determined directly if the recipient's mailbox is available.
Although this may seem limiting, it is quite logic. Think of what spammers would do if this type of verification was available to them.
In order for mailings to give trustable results, make sure you send to people who actually gave their address to you and also make sure you provide a valid "from:" sender address. In the latter case, you will get the 550 errors related to "Recipient not available" bounced in your mailbox.