I have a service which sends email and stores the outbound email in a database. I'm using .NET native Smtp class for email transmission. I have an error flag which is set if the email failed delivery.
My service will periodically check for undelivered messages and try to re-send. Under what circumstances should it retry to send the email? I've noted that even if the email address is incorrect it will thro开发者_开发百科w an exception, but I want my service to ditch any invalid emails otherwise it will retry forever.
Basically, I want to catch the exception whereby there is a good chance the email can be re-delivered. I guess this would be only network errors rather than email accounts. Which of the SmtpStatusCode would indicate worthy of retry:
http://msdn.microsoft.com/en-us/library/system.net.mail.smtpstatuscode.aspx
I'm trying to solve the exact same problem you are. The solution I came up involved about an hour of pouring through RFC2821 and interpreting it as best as I could. If you are interested you can find it here - http://www.rfc-editor.org/rfc/rfc2821.txt
The big take-away from reading the RFC is that codes of the form 4yz
indicate a transient negative completion code, which warrants a retry. Codes of the form 5yz
indicate a permanent negative completion code, which you should NOT retry.
So the goal is to filter out recipients where a retry is warranted and where it isn't. All of the 5yz
codes can be interpreted as "don't retry" with the exception of 552, which some servers may erroneously send instead of 452. From there on out you need to decide:
- Is this a serious technical error (503
BadCommandSequence
) which should cause your service to fail fast without retrying anything.
OR
- Is this a negative permanent completion code which only applies to a single recipient, in which case you just leave out the recipient in question and retry the send operation.
Thus, my approach is such:
- Handle the
SmtpFailedRecipientsException
andSmtpFailedRecipientException
. - Store all failed recipients into a list.
- For each failed recipient, call my special
HandleFailedRecipient()
method which interprets the SMTP code. If at any time that method returnsfalse
then this indicates a much more serious error and my service should just fail fast (without trying to resend the email). - Otherwise, the method may or may not re-add the recipient (depending on the SMTP code).
- Finally, if the message still has any remaining recipients, try to resend.
Here is my method which interprets the SMTP code:
private bool HandleFailedRecipient(MailMessage message, string emailAddress, SmtpStatusCode statusCode, AddressType addressType)
{
//Notify any event subscribers that a recipient failed and give them the SMTP code.
RecipientFailedOnSend(this, new MailAddressEventArgs() { Address = emailAddress, AddressType = addressType, StatusCode = statusCode });
//5yz codes typically indicate a 'Permanent Negative Completion reply', which means we should NOT keep trying to send the message.
//The codes below can be interpreted as exceptions to the rule. In these cases we will just strip the user and try to resend.
if (statusCode == SmtpStatusCode.MailboxUnavailable || //550 = "No such user"
statusCode == SmtpStatusCode.MailboxNameNotAllowed || //553 = "User name ambiguous"
statusCode == SmtpStatusCode.UserNotLocalTryAlternatePath || //551 = "Mail address not deliverable"
statusCode == SmtpStatusCode.TransactionFailed) //554 = "Transaction failed / no valid recipients"
{
return true;
}
//The 4yz codes are 'Transient Negative Completion reply' codes, which means we should re-add the recipient and let the calling routine try again after a timeout.
if (statusCode == SmtpStatusCode.ServiceNotAvailable ||
statusCode == SmtpStatusCode.MailboxBusy ||
statusCode == SmtpStatusCode.LocalErrorInProcessing ||
statusCode == SmtpStatusCode.InsufficientStorage ||
statusCode == SmtpStatusCode.ClientNotPermitted ||
//The ones below are 'Positive Completion reply' 2yz codes. Not likely to occur in this scenario but we will account for them anyway.
statusCode == SmtpStatusCode.SystemStatus ||
statusCode == SmtpStatusCode.HelpMessage ||
statusCode == SmtpStatusCode.ServiceReady ||
statusCode == SmtpStatusCode.ServiceClosingTransmissionChannel ||
statusCode == SmtpStatusCode.Ok ||
statusCode == SmtpStatusCode.UserNotLocalWillForward ||
statusCode == SmtpStatusCode.CannotVerifyUserWillAttemptDelivery ||
statusCode == SmtpStatusCode.StartMailInput ||
statusCode == SmtpStatusCode.CannotVerifyUserWillAttemptDelivery ||
//The code below (552) may be sent by some incorrect server implementations instead of 452 (InsufficientStorage).
statusCode == SmtpStatusCode.ExceededStorageAllocation)
{
if ((addressType & AddressType.To) != 0)
{
message.To.Add(emailAddress);
}
if ((addressType & AddressType.CC) != 0)
{
message.CC.Add(emailAddress);
}
if ((addressType & AddressType.BCC) != 0)
{
message.Bcc.Add(emailAddress);
}
return true;
}
//Anything else indicates a very serious error (probably of the 5yz variety that we haven't handled yet). Tell the calling routine to fail fast.
return false;
}
Let me know if this makes sense or if you need to see more code, but it should be relatively clear-cut from here.
精彩评论