/// <summary> /// Examines an email to try to identify detailed bounce information from it. /// </summary> /// <param name="filename">Path and filename of the file being processed.</param> /// <param name="message">The entire text content for an email (headers and body).</param> /// <returns>An EmailProcessingResult value indicating whether the the email was /// successfully processed or not.</returns> public EmailProcessingDetails ProcessBounceEmail(string message) { EmailProcessingDetails bounceDetails = new EmailProcessingDetails(); MimeMessage msg = MimeMessage.Parse(message); if (msg == null) { bounceDetails.ProcessingResult = EmailProcessingResult.ErrorContent; bounceDetails.BounceIdentifier = Core.Enums.BounceIdentifier.NotIdentifiedAsABounce; return(bounceDetails); } // "x-receiver" should contain what Manta originally set as the "return-path" when sending. MessageHeader returnPath = msg.Headers.GetFirstOrDefault("x-receiver"); if (returnPath == null) { bounceDetails.ProcessingResult = EmailProcessingResult.ErrorNoReturnPath; bounceDetails.BounceIdentifier = Core.Enums.BounceIdentifier.UnknownReturnPath; return(bounceDetails); } string rcptTo = string.Empty; int internalSendID = 0; if (!ReturnPathManager.TryDecode(returnPath.Value, out rcptTo, out internalSendID)) { // Not a valid Return-Path so can't process. bounceDetails.ProcessingResult = EmailProcessingResult.ErrorNoReturnPath; bounceDetails.BounceIdentifier = Core.Enums.BounceIdentifier.UnknownReturnPath; return(bounceDetails); } MantaBounceEvent bounceEvent = new MantaBounceEvent(); bounceEvent.EmailAddress = rcptTo; bounceEvent.SendID = MantaMTA.Core.DAL.SendDB.GetSend(internalSendID).ID; // TODO: Might be good to get the DateTime found in the email. bounceEvent.EventTime = DateTime.UtcNow; // These properties are both set according to the SMTP code we find, if any. bounceEvent.BounceInfo.BounceCode = MantaBounceCode.Unknown; bounceEvent.BounceInfo.BounceType = MantaBounceType.Unknown; bounceEvent.EventType = MantaEventType.Bounce; // First, try to find a NonDeliveryReport body part as that's the proper way for an MTA // to tell us there was an issue sending the email. BouncePair bouncePair; string bounceMsg; BodyPart deliveryReportBodyPart; string deliveryReport = string.Empty; if (FindFirstBodyPartByMediaType(msg.BodyParts, "message/delivery-status", out deliveryReportBodyPart)) { // If we've got a delivery report, check it for info. // Abuse report content may have long lines whitespace folded. deliveryReport = MimeMessage.UnfoldHeaders(deliveryReportBodyPart.GetDecodedBody()); if (ParseNdr(deliveryReport, out bouncePair, out bounceMsg, out bounceDetails)) { // Successfully parsed. bounceEvent.BounceInfo = bouncePair; bounceEvent.Message = bounceMsg; // Write BounceEvent to DB. Save(bounceEvent); bounceDetails.ProcessingResult = EmailProcessingResult.SuccessBounce; return(bounceDetails); } } // We're still here so there was either no NDR part or nothing contained within it that we could // interpret so have to check _all_ body parts for something useful. if (FindBounceReason(msg.BodyParts, out bouncePair, out bounceMsg, out bounceDetails)) { bounceEvent.BounceInfo = bouncePair; bounceEvent.Message = bounceMsg; // Write BounceEvent to DB. Save(bounceEvent); bounceDetails.ProcessingResult = EmailProcessingResult.SuccessBounce; return(bounceDetails); } // Nope - no clues relating to why the bounce occurred. bounceEvent.BounceInfo.BounceType = MantaBounceType.Unknown; bounceEvent.BounceInfo.BounceCode = MantaBounceCode.Unknown; bounceEvent.Message = string.Empty; bounceDetails.BounceIdentifier = Core.Enums.BounceIdentifier.NotIdentifiedAsABounce; bounceDetails.ProcessingResult = EmailProcessingResult.Unknown; return(bounceDetails); }
/// <summary> /// Examines an SMTP response message to identify detailed bounce information from it. /// </summary> /// <param name="response">The message that's come back from an external MTA when attempting to send an email.</param> /// <param name="rcptTo">The email address that was being sent to.</param> /// <param name="internalSendID">The internal Manta SendID.</param> /// <returns>True if a bounce was found and recorded, false if not.</returns> internal bool ProcessSmtpResponseMessage(string response, string rcptTo, int internalSendID, out EmailProcessingDetails bounceIdentification) { bounceIdentification = new EmailProcessingDetails(); // Check for TimedOutInQueue message first. if (response.Equals(MtaParameters.TIMED_OUT_IN_QUEUE_MESSAGE, StringComparison.OrdinalIgnoreCase)) { bounceIdentification = null; MantaTimedOutInQueueEvent timeOut = new MantaTimedOutInQueueEvent { EventType = MantaEventType.TimedOutInQueue, EmailAddress = rcptTo, SendID = SendDB.GetSend(internalSendID).ID, EventTime = DateTime.UtcNow }; // Log to DB. Save(timeOut); // All done return true. return(true); } BouncePair bouncePair = new BouncePair(); string bounceMessage = string.Empty; if (ParseBounceMessage(response, out bouncePair, out bounceMessage, out bounceIdentification)) { // Were able to find the bounce so create the bounce event. MantaBounceEvent bounceEvent = new MantaBounceEvent { EventType = MantaEventType.Bounce, EmailAddress = rcptTo, BounceInfo = bouncePair, SendID = SendDB.GetSend(internalSendID).ID, // It is possible that the bounce was generated a while back, but we're assuming "now" for the moment. // Might be good to get the DateTime found in the email at a later point. EventTime = DateTime.UtcNow, Message = response }; // Log to DB. Save(bounceEvent); // All done return true. return(true); } // Couldn't identify the bounce. bounceIdentification.BounceIdentifier = Core.Enums.BounceIdentifier.NotIdentifiedAsABounce; return(false); }