private static void DeliverTicketEventNotificationEmail(TicketDataDataContext ctx, TicketEventNotification note, List<TicketEventNotification> consolidations) { bool status = false; try { //TODO: Modify body generation to flag comments for the current event and all consolidations. TicketComment comment = note.TicketComment; Ticket ticket = comment.Ticket; SmtpClient client = new SmtpClient(); string url = null; string rootUrl = ConfigurationManager.AppSettings["WebRootUrlForEmailLinks"]; if (!string.IsNullOrEmpty(rootUrl)) { if (!rootUrl.EndsWith("/")) { rootUrl += "/"; } url = string.Format("{0}ViewTicket.aspx?id={1}", rootUrl, ticket.TicketId.ToString()); } int minComment = note.CommentId; if (consolidations.Count() > 0) { minComment = consolidations.Min(c => c.CommentId); } string body = NotificationUtilities.GetHTMLBody(ticket, url, note.NotifyUser, minComment); string displayFrom = ConfigurationManager.AppSettings["FromEmailDisplayName"]; string addressFrom = ConfigurationManager.AppSettings["FromEmailAddress"]; string blindCopyTo = ConfigurationManager.AppSettings["BlindCopyToEmailAddress"]; MailAddress bccAddr = null; if (!string.IsNullOrEmpty(blindCopyTo)) { bccAddr = new MailAddress(blindCopyTo); } MailAddress fromAddr = new MailAddress(addressFrom, displayFrom); string tStat = ticket.CurrentStatus; if (string.IsNullOrEmpty(ticket.AssignedTo)) { tStat = (ticket.TicketComments.Count() < 2)? "New" : "Unassigned"; } string subject = string.Format("Ticket {0} ({1}): {2}", ticket.TicketId.ToString(), tStat, ticket.Title); MailAddress toAddr = new MailAddress(note.NotifyEmail, note.NotifyUserDisplayName); MailMessage msg = new MailMessage(fromAddr, toAddr); if (bccAddr != null) { msg.Bcc.Add(bccAddr); } msg.Subject = subject; msg.Body = body; //body; msg.IsBodyHtml = true; client.Send(msg); status = true; } catch (Exception ex) { Exception newEx = new ApplicationException("Failure in Email Delivery Timer", ex); HttpContext mockContext = (new TicketDesk.Engine.MockHttpContext(false)).Context; Elmah.ErrorLog.GetDefault(mockContext).Log(new Elmah.Error(newEx)); } DateTime now = DateTime.Now; note.LastDeliveryAttemptDate = now; foreach (var consolidateNote in consolidations) { consolidateNote.LastDeliveryAttemptDate = now; } if (!status) { if (note.Status == "final-attempt") { note.Status = "failed"; foreach (var consolidateNote in consolidations) { consolidateNote.Status = "consolidated-failed"; } } if (note.Status == "pending") { note.Status = "queued"; } //check for notes that queued after the send process first started here, and increment their nextDeliveryDate so they don't cause // resends to happen sooner than the next delivery retry interval warrants. TicketDataDataContext ctxNew = new TicketDataDataContext();//existing context will have cached data from the past read. Do this on new context var newNotes = from tn in ctxNew.TicketEventNotifications where tn.CommentId > note.CommentId && tn.NextDeliveryAttemptDate <= note.NextDeliveryAttemptDate select tn; if (newNotes.Count() > 0) { int advanceSeconds = 1; foreach (var newNote in newNotes) { newNote.NextDeliveryAttemptDate = note.NextDeliveryAttemptDate.Value.AddSeconds(advanceSeconds); advanceSeconds++; } ctxNew.SubmitChanges(); } } else { note.Status = "sent"; note.NextDeliveryAttemptDate = null; foreach (var consolidateNote in consolidations) { consolidateNote.Status = "consolidated"; consolidateNote.NextDeliveryAttemptDate = null; } } ctx.SubmitChanges();//submit changes ticket-by-ticket so a failure later doesn't scrap the status update for tickets previously sent }
private static void SendTicketEventNotificationEmail(TicketDataDataContext ctx, TicketEventNotification note, List<TicketEventNotification> consolidations) { sendingLock = new object(); lock (sendingLock) { PrepareTicketEventNotificationForDelivery(ctx, note, consolidations); DeliverTicketEventNotificationEmail(ctx, note, consolidations); } }
private static void PrepareTicketEventNotificationForDelivery(TicketDataDataContext ctx, TicketEventNotification note, List<TicketEventNotification> consolidations) { string resendDelaySetting = ConfigurationManager.AppSettings["EmailResendDelayMinutes"]; double resendDelay = 5D; if (!string.IsNullOrEmpty(resendDelaySetting)) { resendDelay = Convert.ToDouble(resendDelay); } string maxRetriesSetting = ConfigurationManager.AppSettings["EmailMaxDeliveryAttempts"]; int maxRetries = 5; if (!string.IsNullOrEmpty(maxRetriesSetting)) { maxRetries = Convert.ToInt32(maxRetriesSetting); } note.DeliveryAttempts += 1; if (note.DeliveryAttempts < maxRetries) { note.Status = "pending"; note.NextDeliveryAttemptDate = DateTime.Now.AddMinutes(resendDelay * note.DeliveryAttempts); int backTrackSeconds = -1; foreach (var consolidateNote in consolidations.OrderByDescending(c => c.CommentId)) { //this will reorder the next delivery attempt date for consolidations in descending order keeping the relative order for them all consolidateNote.NextDeliveryAttemptDate = note.NextDeliveryAttemptDate.Value.AddSeconds(backTrackSeconds); consolidateNote.DeliveryAttempts += 1; backTrackSeconds--; } } else { note.Status = "final-attempt"; note.NextDeliveryAttemptDate = null; foreach (var consolidateNote in consolidations) { consolidateNote.NextDeliveryAttemptDate = null; consolidateNote.DeliveryAttempts += 1; } } ctx.SubmitChanges();//submit changes before email attempt... this way if there is an unhandled failure, the ticket will still have incremented the delivery attempt and next retry values (prevents accidental spam) /* Unhandled edge case: * We've set the next delivery date for the note we are sending to a future date in case something seriously goes wrong during sending the email. * This is important because if the sending routine exits and we hadn't committed a next delivery date and all that, it would potentially cause * an inifite retry-loop. * * The edge case is that new notes could have been queued after we read the queue from the DB into memory. Those notes would have a delivery * date much lower than the retry we just scheduled for this note. We could try to predict and work around this potential case, but it is * such an extreme edge case (something has to queue AND an unhandled exception has to happen in the sending routine both). * * By not handling this here, the worst that can happen is that the new queued note will simply cause a retry far sooner than we wanted. * A work-around for this case has a significant perforance impact though, and so we are not going to implement that here... besides, if * there is an unhandled exception in the send routine it will likely be something so severe that future send attemps are all going to fail * for this user's notes indefinitly until the issue is debugged or a major data corruption issue is fixed anyway. */ }
private void detach_TicketEventNotifications(TicketEventNotification entity) { this.SendPropertyChanging(); entity.TicketComment = null; }
partial void DeleteTicketEventNotification(TicketEventNotification instance);
partial void UpdateTicketEventNotification(TicketEventNotification instance);
partial void InsertTicketEventNotification(TicketEventNotification instance);
private TicketEventNotification CreateTicketEventNotificationForUser(int commentId, string commentBy, string user, string userType) { TicketEventNotification note = null; if (!string.IsNullOrEmpty(user)) { bool emailValid = false; string email = SecurityManager.GetUserEmailAddress(user); // replaced pattern with variation based on the stock MS regex validator control's built-in pattern for email addresses //var rxv = new RegexStringValidator(@"^[a-zA-Z\.\-_]+@([a-zA-Z\.\-_]+\.)+[a-zA-Z]{2,4}$"); var rxv = new RegexStringValidator(@"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$"); try { rxv.Validate(email); emailValid = true; } catch { } note = new TicketEventNotification(); note.TicketId = TicketId; note.CommentId = commentId; note.NotifyUser = SecurityManager.GetFormattedUserName(user); note.NotifyUserDisplayName = SecurityManager.GetUserDisplayName(user); note.NotifyUserReason = userType; note.EventGeneratedByUser = commentBy; if (!string.IsNullOrEmpty(email) && emailValid) { note.NotifyEmail = email; } else { note.NotifyEmail = "invalid"; } } return note; }