Home » Articles

SmtpFailedRecipientsException - Catch undeliverable mail

3. October 2008 by Juliën Hanssens 2 Comments

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:

  1. Send an e-mail to multiple recipients, one-by-one
  2. Catch the ones that fail
  3. Try to re-send failed deliveries that can be handled (i.e. MailboxBusy)
  4. 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 nameDescription
  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.

Comments

Kalel
Brazil Kalel said:

Sounds great, but really doesn't work for me at all.

The error only is detected when a e-mail have a domain inexistent like in your sample: johndoe@nonexistingdomain.com, but if I try to send a mail like johndoe_nonexistingmail@microsoft.com, the result it will be no errors detected and the I will recieve a 'bounce mail', and that is the problem, I wouldn't like to recieve theses bounced mails, I just like to exactly what you're telling in the begging of the article:

"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!"

May you help me please?

It's really important.

Thanks in advance!

Kalel

Juliën Hanssens
Netherlands Juliën Hanssens said:

Kalel: you are right. An update to the article has been provided, considering that you cannot detect non-existing mailboxes using these classes prior to sending them. You will only receive bounced e-mails by a reply from the server after it has been sent.

Comments are closed