internal static void MailSent(IAsyncResult result) { lock (lockObject) { MailTransmissionAsyncState asyncState = (MailTransmissionAsyncState)result.AsyncState; MailTransmissionDelegate asyncTransmitter = asyncState.dlgt; OutboundMailRecipient recipient = asyncState.recipient; try { asyncTransmitter.EndInvoke(result); try { recipient.Delete(); recipient.OutboundMail.IncrementSuccesses(); } catch (Exception ex) { // whatever.. The mail is sent, but we couldn't mark it as such, probably a database failure. asyncState.exception = new RemoveRecipientException("Couldn't remove mail recipient.", ex); ExceptionMail.Send(asyncState.exception, true); } } catch (ReportAndRetryRecipientException e) { asyncState.exception = e; Debug.WriteLine(e.ToString()); ExceptionMail.Send(e, true); } catch (RetryRecipientException e) { // No action - handled in async delegate - retry this mail asyncState.exception = e; } catch (Exception e) { asyncState.exception = e; try { recipient.Delete(); recipient.OutboundMail.IncrementFailures(); if (e is InvalidRecipientException) { if (recipient.Person != null && recipient.Person.EMailIsInvalid == false) { //Mark address as invalid. recipient.Person.EMailIsInvalid = true; } } } catch (Exception ex) { // Return this exception in asyncState since it is important to stop flooding. asyncState.exception = new RemoveRecipientException("Couldn't remove mail recipient after exception.", ex); ExceptionMail.Send(asyncState.exception, true); //Report the secondary exception } Debug.WriteLine(e.ToString()); ExceptionMail.Send(e, true); } asyncState.callbackCompleted = true; } }
internal static OutboundMailRecipient TransmitOneMail(OutboundMailRecipient recipient) { try { // If the mail address in illegal format, do not try to send anything: if (!Formatting.ValidateEmailFormat(recipient.EmailPerson.Email.Trim())) { string msg = "Invalid email address:\r\nEmailPerson [" + recipient.EmailPerson.Identity + "], mail [" + recipient.EmailPerson.Email + "]\r\nwill not send mail:" + recipient.OutboundMail.Title; throw new InvalidRecipientException(msg, null); } // If the mail address is marked as unreachable, do not try to send anything if (recipient.Person != null && recipient.Person.MailUnreachable) { string msg = "MailUnreachable email address:\r\nEmailPerson [" + recipient.EmailPerson.Identity + "], mail [" + recipient.EmailPerson.Email + "]\r\nwill not send mail:" + recipient.OutboundMail.Title; throw new InvalidRecipientException(msg, null); } // If the mail address is marked as unreachable, do not try to send anything if (recipient.Person != null && recipient.Person.NeverMail) { string msg = "NeverMail email address:\r\nEmailPerson [" + recipient.EmailPerson.Identity + "], mail [" + recipient.EmailPerson.Email + "]\r\nwill not send mail:" + recipient.OutboundMail.Title; throw new IgnoreRecipientException(msg, null); } // Otherwise, let's start processing OutboundMail mail = recipient.OutboundMail; bool limitToLatin1 = false; bool limitToText = false; Encoding currentEncoding = Encoding.UTF8; string email = recipient.EmailPerson.Email.ToLower(); if (mail.MailType == 0 || mail.TemplateName.EndsWith("Plain")) { limitToText = true; } // TEST: Does this user require the use of a text-only message (as opposed to multipart/alternative)? if (recipient.Person != null && recipient.Person.LimitMailToText) { limitToText = true; } // This is supposedly not a problem anymore //if (email.EndsWith("@hotmail.com") || email.EndsWith("@msn.com")) //{ // limitToLatin1 = true; //} // TEST: Does this user require the limited use of the Latin-1 charset (as opposed to Unicode?) if (recipient.Person != null && recipient.Person.LimitMailToLatin1) { limitToLatin1 = true; } // Capability tests end here if (limitToLatin1) { currentEncoding = Encoding.GetEncoding("ISO-8859-1"); } else { currentEncoding = Encoding.UTF8; } QuotedPrintable qp = QuotedPrintableEncoder[currentEncoding]; MailMessage message = new MailMessage(); if (mail.AuthorType == MailAuthorType.Person) { try { message.From = new MailAddress(mail.Author.PartyEmail, qp.EncodeMailHeaderString(mail.Author.Name + " (" + mail.Organization.MailPrefixInherited + ")"), currentEncoding); if (mail.Author.Identity == 1) { //TODO: Create alternative party mail optional data field, or organization chairman (based on roles) differently // Ugly hack message.From = new MailAddress("*****@*****.**", qp.EncodeMailHeaderString(mail.Author.Name + " (" + mail.Organization.MailPrefixInherited + ")"), currentEncoding); } } catch (Exception ex) { throw new InvalidSenderException( "Invalid author address in MailProcessor.TransmitOneMail:" + (mail.AuthorPersonId) + ";" + mail.Author.PartyEmail, ex); } } else { try { FunctionalMail.AddressItem aItem = mail.Organization.GetFunctionalMailAddressInh(mail.AuthorType); message.From = new MailAddress(aItem.Email, qp.EncodeMailHeaderString(aItem.Name), currentEncoding); } catch (Exception ex) { throw new InvalidSenderException( "Unknown MailAuthorType in MailProcessor.TransmitOneMail:" + ((int)mail.AuthorType), ex); } } if (recipient.AsOfficer && recipient.Person != null) { try { message.To.Add(new MailAddress(recipient.Person.PartyEmail, qp.EncodeMailHeaderString(recipient.Person.Name + " (" + mail.Organization.MailPrefixInherited + ")"), currentEncoding)); } catch (FormatException e) { string msg = "Invalid officer email address:\r\nperson [" + recipient.Person.Identity + "], mail [" + recipient.Person.PartyEmail + "]\r\nwill not send mail:" + recipient.OutboundMail.Title; throw new InvalidRecipientException(msg, e); } } else { try { message.To.Add(new MailAddress(recipient.EmailPerson.Email, qp.EncodeMailHeaderString(recipient.EmailPerson.Name), currentEncoding)); } catch (FormatException e) { string msg = "Invalid email address:\r\nEmailPerson [" + recipient.EmailPerson.Identity + "], mail [" + recipient.EmailPerson.Email + "]\r\nwill not send mail:" + recipient.OutboundMail.Title; throw new InvalidRecipientException(msg, e); } } string culture = mail.Organization.DefaultCountry.Culture; // UGLY UGLY UGLY HACK, NEEDS TO CHANGE ASAP: // We need to determine the culture of the recipient in order to use the right template. However, this is also dependent on the text body, which needs to be // in the same culture. At this point, we don't have the mail/recipient cultures in the schema. This would be the correct solution. // The INCORRECT but working solution is to do as we do here and check if a) it's a reporter and b) the reporter has International/English as a category. If so, // we change the culture to en-US. It's an ugly as all hell hack but it should work as a temporary stopgap. if (recipient.Reporter != null) { MediaCategories categories = recipient.Reporter.MediaCategories; foreach (MediaCategory category in categories) { if (category.Name == "International/English") { culture = Strings.InternationalCultureCode; break; } } } if (limitToText) { // if just text, then just add a plaintext body; string text = ""; //Cant really see any reson the HtmlAgilityPack shouldn't be thread safe, but what the heck, just in case.. lock (lockObject) { try { text = mail.RenderText(recipient.EmailPerson, culture); } catch (Exception ex) { throw new RemoveRecipientException( "TextRendering failed for " + mail.Title + " to " + recipient.EmailPerson.Email + " will not retry.\n", ex); } } message.BodyEncoding = currentEncoding; message.Body = text; } else { // otherwise, add a multipart/alternative with text and HTML string text = ""; string html = ""; //Cant really see any reson the HtmlAgilityPack shouldn't be thread safe, but what the heck, just in case.. Exception ex = null; lock (lockObject) { try { text = mail.RenderText(recipient.EmailPerson, culture); html = mail.RenderHtml(recipient.EmailPerson, culture); } catch (Exception e) { ex = e; } } if (text == "") { throw new RemoveRecipientException( "Rendering (text) failed for " + mail.Title + " to " + recipient.EmailPerson.Email + " will not retry.\n", ex); } if (html == "" || ex != null) { throw new RemoveRecipientException( "Rendering (html) failed for " + mail.Title + " to " + recipient.EmailPerson.Email + " will not retry.\n", ex); } ContentType textContentType = new ContentType(MediaTypeNames.Text.Plain); textContentType.CharSet = currentEncoding.BodyName; ContentType htmlContentType = new ContentType(MediaTypeNames.Text.Html); htmlContentType.CharSet = currentEncoding.BodyName; AlternateView textView = null; AlternateView htmlView = null; if (limitToLatin1) { textView = new AlternateView(new MemoryStream(currentEncoding.GetBytes(text)), textContentType); htmlView = new AlternateView(new MemoryStream(currentEncoding.GetBytes(text)), htmlContentType); } else { textView = AlternateView.CreateAlternateViewFromString(text, textContentType); htmlView = AlternateView.CreateAlternateViewFromString(html, htmlContentType); } // A f*****g stupid Mono bug forces us to transfer-encode in base64: it can't encode qp properly // (the "=" is not encoded to "=3D") htmlView.TransferEncoding = TransferEncoding.Base64; textView.TransferEncoding = TransferEncoding.Base64; // Add the views in increasing order of preference message.AlternateViews.Add(textView); message.AlternateViews.Add(htmlView); } if (mail.AuthorType == MailAuthorType.PirateWeb) { message.Subject = mail.Title; } else if (mail.MailType == 0) { message.Subject = mail.Organization.MailPrefixInherited + ": " + mail.Title; } else { //Title is set up in template processing in OutboundMail rendering. message.Subject = mail.Title; } message.SubjectEncoding = currentEncoding; string smtpServer = ConfigurationManager.AppSettings["SmtpServer"]; if (Debugger.IsAttached) { Debug.WriteLine("sending " + message.Subject + " to " + recipient.EmailPerson.Email); Thread.Sleep(200); //simulate delay } if (smtpServer.ToLower() != "none") { if (smtpServer == null || smtpServer.Length < 2) { smtpServer = "localhost"; } try { SmtpClient client = new SmtpClient(smtpServer, 25); client.Send(message); } catch (SmtpException e) { if (e.ToString().StartsWith("System.Net.Mail.SmtpException: 4")) { // Temporary error (SMTP 4xx). Try again. Thread.Sleep(2000); // Allow 2 seconds pause to wait for smtp-server to become available throw new ReportAndRetryRecipientException("Temporary smtp error, will retry.", e); } // Otherwise, bad recipient (assume so). Have the mail removed from the queue. List <string> recipients = new List <string>(); foreach (MailAddress address in message.To) { recipients.Add(address.Address); } ExceptionMail.Send( new ArgumentException( "Bad Recipients when sending to " + recipient.EmailPerson.Email + ": " + String.Join(", ", recipients.ToArray()), e)); if (mail.AuthorType == MailAuthorType.Person) { try { mail.Author.SendOfficerNotice( "Failed recipient(s): " + String.Join(", ", recipients.ToArray()), "Some recipients failed inexplicably in a mail from you.", 1); } catch (Exception ex) { throw new Exception("Failed to SendOfficerNotice to :" + mail.AuthorPersonId, ex); } } } } return(recipient); // To pass this object onto the we're-done callback } catch (InvalidRecipientException ex) { throw ex; } catch (RetryRecipientException ex) { Thread.Sleep(2000); // Allow 2 seconds pause to avoid flooding the errorlog too fast in case of a permanent failure throw ex; } catch (Exception ex) { throw ex; } }