public IActionResult EditListPost(string id, MailListModel model, string action) { if (action == "delete") { return(EditListDelete(id)); } try { model.Value.Name = model.Value.Name?.Trim(); if (model.Value.Name.Length > 16) { throw new ArgumentException(Resources.NameIsTooLong.FormatHtml(16)); } else if (!model.Value.FromEmailAddress.TryParseEmailAddress(out _)) { throw new ArgumentException(Resources.EmailIsInvalid); } model.Value.Company = model.Value.Company?.Trim(); model.Value.Website = model.Value.Website?.Trim(); if (!MailTemplate.ValidateName(model.Value.Name)) { throw new ArgumentException(Resources.NameInvalidChars); } using (MailDemonDatabase db = dbProvider.GetDatabase()) { MailList existingList = db.Lists.FirstOrDefault(l => l.Name == model.Value.Name); if (existingList != null && (existingList.Name != model.Value.Name || model.Value.Id == 0)) { throw new ArgumentException(Resources.NameCannotChange); } if (model.Value.Id == 0) { db.Lists.Add(model.Value); } else { db.Update(model.Value); } db.SaveChanges(); } TempData["Message"] = Resources.Success; return(RedirectToAction(nameof(EditList), new { id = model.Value.Name })); } catch (Exception ex) { MailDemonLog.Error(ex); model.Error = true; model.Message = ex.Message; return(View(model)); } }
public IActionResult Subscribers(string id) { id = (id ?? string.Empty).Trim(); if (id.Length == 0) { return(NotFound()); } using MailDemonDatabase db = dbProvider.GetDatabase(); MailList list = db.Lists.FirstOrDefault(l => l.Name == id); if (list == null) { return(NotFound()); } ICollection <MailListSubscription> subscribers = db.Subscriptions.Where(s => s.ListName == id).OrderByDescending(s => s.Result).ThenByDescending(s => s.Id).Take(1000).ToList(); ViewBag.ListName = id; return(View(subscribers)); }
private IActionResult EditListDelete(string id) { try { using MailDemonDatabase db = dbProvider.GetDatabase(); MailList list = db.Lists.FirstOrDefault(l => l.Name == id); if (list != null) { db.Subscriptions.RemoveRange(db.Subscriptions.Where(r => r.ListName == id)); db.Templates.RemoveRange(db.Templates.Where(t => t.Name.StartsWith(list.Name + MailTemplate.FullNameSeparator))); db.Lists.Remove(list); db.SaveChanges(); } } catch (Exception ex) { MailDemonLog.Error(ex); } return(RedirectToAction(nameof(EditList))); }
/// <summary> /// Bulk email /// </summary> /// <param name="db">Database</param> /// <param name="list">List</param> /// <param name="unsubscribeUrl">Unsubscribe url</param> /// <param name="all">True to email all, false to only email error registrations (those that have not yet or failed to send)</param> /// <returns>Subscriptions to send to, grouped by domain</returns> public static IEnumerable <KeyValuePair <string, List <MailListSubscription> > > BeginBulkEmail(this MailDemonDatabase db, MailList list, string unsubscribeUrl, bool all) { if (all) { db.Database.ExecuteSqlRaw("UPDATE Subscriptions SET Result = 'Pending', ResultTimestamp = {0} WHERE ListName = {1}", DateTime.UtcNow, list.Name); } else { db.Database.ExecuteSqlRaw("UPDATE Subscriptions SET Result = 'Pending', ResultTimestamp = {0} WHERE ListName = {1} AND Result <> ''", DateTime.UtcNow, list.Name); } List <MailListSubscription> subs = new List <MailListSubscription>(); string domain = null; foreach (MailListSubscription sub in db.Subscriptions.Where(s => s.ListName == list.Name && s.Result == "Pending") .OrderBy(s => s.EmailAddressDomain)) { if (domain is null || sub.EmailAddressDomain != domain) { if (subs.Count != 0) { yield return(new KeyValuePair <string, List <MailListSubscription> >(domain, subs)); subs = new List <MailListSubscription>(); } domain = sub.EmailAddressDomain; } sub.MailList = list; sub.UnsubscribeUrl = string.Format(unsubscribeUrl, sub.UnsubscribeToken); subs.Add(sub); } if (subs.Count != 0) { yield return(new KeyValuePair <string, List <MailListSubscription> >(domain, subs)); } }
public async Task SendBulkMail(MailList list, IMailCreator mailCreator, IMailSender mailSender, ExpandoObject viewBag, bool all, string fullTemplateName, string unsubscribeUrl) { MailDemonLog.Warn("Started bulk send for {0}", fullTemplateName); DateTime now = DateTime.UtcNow; int successCount = 0; int failCount = 0; List <Task> pendingTasks = new List <Task>(); Stopwatch timer = Stopwatch.StartNew(); using (var db = dbProvider.GetDatabase()) { void callbackHandler(MailListSubscription _sub, string error) { lock (db) { // although this is slow, it is required as we do not want to double email people in the event // that server reboots, loses power, etc. for every message we have to mark that person // with the correct status immediately _sub.Result = error; _sub.ResultTimestamp = DateTime.UtcNow; db.Update(_sub); db.SaveChanges(); if (string.IsNullOrWhiteSpace(error)) { successCount++; } else { failCount++; } } } // use a separate database instance to do the query, that way we can update records in our other database instance // preventing locking errors, especially with sqlite drivers MailDemonLog.Warn("Begin bulk send"); using (var dbBulk = dbProvider.GetDatabase()) { IEnumerable <KeyValuePair <string, IEnumerable <MailListSubscription> > > pendingSubs = dbBulk.GetBulkEmailSubscriptions(list, unsubscribeUrl, all); foreach (KeyValuePair <string, IEnumerable <MailListSubscription> > sub in pendingSubs) { now = DateTime.UtcNow; try { IAsyncEnumerable <MailToSend> messagesToSend = GetMessages(sub.Value, mailCreator, list, viewBag, fullTemplateName, callbackHandler); Task task = mailSender.SendMailAsync(sub.Key, messagesToSend); pendingTasks.Add(task); } catch (Exception ex) { MailDemonLog.Error(ex); } } } await Task.WhenAll(pendingTasks); MailDemonLog.Warn("Finished bulk send for {0}, {1} messages succeeded, {2} messages failed in {3:0.00} seconds.", fullTemplateName, successCount, failCount, timer.Elapsed.TotalSeconds); } GC.Collect(); }
private async IAsyncEnumerable <MailToSend> GetMessages(IEnumerable <MailListSubscription> subs, IMailCreator mailCreator, MailList list, ExpandoObject viewBag, string fullTemplateName, Action <MailListSubscription, string> callback) { foreach (MailListSubscription sub in subs) { MimeMessage message; try { message = await mailCreator.CreateMailAsync(fullTemplateName, sub, viewBag, null); } catch (Exception ex) { MailDemonLog.Error(ex); continue; } message.From.Clear(); message.To.Clear(); if (string.IsNullOrWhiteSpace(list.FromEmailName)) { message.From.Add(MailboxAddress.Parse(list.FromEmailAddress)); } else { message.From.Add(new MailboxAddress(list.FromEmailName, list.FromEmailAddress)); } message.To.Add(MailboxAddress.Parse(sub.EmailAddress)); yield return(new MailToSend { Subscription = sub, Message = message, Callback = callback }); } }