GetSubscribersAsync(EmailReminderFilter filter) { return(await _emailReminderRepository .GetListSubscribersAsync(filter.MailingList, filter.Skip ?? 0, filter.Take ?? 30)); }
GetSubscribersWithCountAsync(EmailReminderFilter filter) { return(new DataWithCount <ICollection <EmailReminder> > { Count = await _emailReminderRepository .GetListSubscribersCountAsync(filter.MailingList), Data = await GetSubscribersAsync(filter) }); }
private async Task <JobStatus> SendBulkListAsync(int userId, int jobId, IProgress <JobStatus> progress, JobDetailsSendBulkEmails jobDetails, CancellationToken token) { var sw = System.Diagnostics.Stopwatch.StartNew(); int emailsSent = 0; int emailsSkipped = 0; int userCounter = 0; int addSentCounter = 0; var problemEmails = new HashSet <string>(); var filter = new EmailReminderFilter { MailingList = jobDetails.MailingList, Take = 30 }; var subscribers = await _emailReminderService.GetSubscribersWithCountAsync(filter); var subscribedCount = subscribers.Count; var subscribed = subscribers.Data; _logger.LogInformation("Email job {JobId}: found {Count} subscribed users, processing first batch of {BatchCount}", jobId, subscribedCount, subscribed.Count); token.Register(() => { _logger.LogWarning("Email job {JobId} for user {UserId} was cancelled after {EmailsSent} sent, {EmailsSkipped} skipped of {SubscribedUsersCount} total in {TimeElapsed}.", jobId, userId, emailsSent, emailsSkipped, subscribedCount, sw.Elapsed.ToString(SpanFormat, CultureInfo.InvariantCulture)); }); if (subscribed.Count > 0) { var site = await _siteLookupService.GetByIdAsync(GetCurrentSiteId()); var emailDetails = new DirectEmailDetails(site.Name) { IsBulk = true, SendingUserId = userId, DirectEmailTemplateId = jobDetails.EmailTemplateId }; var elapsedStatus = sw.Elapsed; var elapsedUpdateDbStatus = sw.Elapsed; var elapsedLogInfoStatus = sw.Elapsed; var elapsedLogInfoPercent = 0; progress.Report(new JobStatus { PercentComplete = 0, Title = $"Sending email...", Status = $"Preparing to email {subscribed.Count} participants...", Error = false }); while (subscribed.Count > 0) { foreach (var emailReminder in subscribed) { if (problemEmails.Contains(emailReminder.Email)) { emailsSkipped++; continue; } bool clearToSend = true; var isParticipant = await _userService .IsEmailSubscribedAsync(emailReminder.Email); if (isParticipant) { clearToSend = false; } if (emailReminder.SentAt != null || !clearToSend) { // send email _logger.LogTrace("Email job {JobId}: skipping email {Count}/{Total} to {Email}: {Message}", jobId, userCounter + 1, subscribedCount, emailReminder.Email, emailReminder.SentAt != null ? " already sent at " + emailReminder.SentAt : " is a subscribed participant"); emailsSkipped++; } else { // send email _logger.LogTrace("Email job {JobId}: sending email {Count}/{Total} to {Email} with template {EmailTemplate}", jobId, userCounter + 1, subscribedCount, emailReminder.Email, jobDetails.EmailTemplateId); // send email to user try { emailDetails.ToAddress = emailReminder.Email; emailDetails.LanguageId = emailReminder.LanguageId; emailDetails.ClearTags(); emailDetails.SetTag("Email", emailReminder.Email); DirectEmailHistory result = null; try { result = await _emailService.SendDirectAsync(emailDetails); await _emailReminderService.UpdateSentDateAsync(emailReminder.Id); } catch (Exception ex) { _logger.LogWarning("Unable to email {ToAddress}: {ErrorMessage}", emailDetails.ToAddress, ex.Message); } if (result?.Successful == true) { addSentCounter++; emailsSent++; } else { problemEmails.Add(emailReminder.Email); } } catch (Exception ex) { _logger.LogError(ex, "Email job {JobId}: Send failed to {UserId} at {Email}: {ErrorMessage}", jobId, emailReminder.Id, emailReminder.Email, ex.Message); problemEmails.Add(emailReminder.Email); } } userCounter++; if (token.IsCancellationRequested) { break; } if (sw.Elapsed.TotalSeconds - elapsedStatus.TotalSeconds > 5 || userCounter == 1) { elapsedStatus = sw.Elapsed; var remaining = TimeSpan .FromMilliseconds(elapsedStatus.TotalMilliseconds / userCounter * (subscribedCount - userCounter)) .ToString(SpanFormat, CultureInfo.InvariantCulture); var status = new JobStatus { PercentComplete = userCounter * 100 / subscribedCount, Status = $"Sent {emailsSent}, skipped {emailsSkipped} of {subscribedCount}; {elapsedStatus.ToString(SpanFormat, CultureInfo.InvariantCulture)}, remaining: {remaining}, problems: {problemEmails.Count}", Error = false }; progress.Report(status); if (sw.Elapsed.TotalSeconds - elapsedUpdateDbStatus.TotalSeconds > 60 || userCounter == 1) { elapsedUpdateDbStatus = sw.Elapsed; if (addSentCounter > 0) { await _emailService.IncrementSentCountAsync( jobDetails.EmailTemplateId, addSentCounter); addSentCounter = 0; } var dbStatusText = string.Format(CultureInfo.InvariantCulture, "{0}%: {1}", status.PercentComplete, status.Status); await _jobRepository.UpdateStatusAsync(jobId, dbStatusText[..Math.Min(dbStatusText.Length, 255)]);
private async Task <JobStatus> SendBulkListAsync(int userId, int jobId, CancellationToken token, IProgress <JobStatus> progress, JobDetailsSendBulkEmails jobDetails) { var sw = new System.Diagnostics.Stopwatch(); sw.Start(); int emailsSent = 0; int emailsSkipped = 0; int userCounter = 0; int addSentCounter = 0; var problemEmails = new List <string>(); var filter = new EmailReminderFilter { MailingList = jobDetails.MailingList, Take = 30 }; var subscribers = await _emailReminderService.GetSubscribersWithCountAsync(filter); var subscribedCount = subscribers.Count; var subscribed = subscribers.Data; _logger.LogInformation("Email job {JobId}: found {Count} subscribed users, processing first batch of {BatchCount}", jobId, subscribedCount, subscribed.Count); token.Register(() => { _logger.LogWarning("Email job {JobId} for user {UserId} was cancelled after {EmailsSent} sent, {EmailsSkipped} skipped of {SubscribedUsersCount} in {TimeElapsed}.", jobId, userId, emailsSent, emailsSkipped, subscribedCount, sw.Elapsed.ToString(SpanFormat, CultureInfo.InvariantCulture)); }); if (subscribed.Count > 0) { var elapsedStatus = sw.Elapsed; var elapsedUpdateDbStatus = sw.Elapsed; var elapsedLogInfoStatus = sw.Elapsed; var elapsedLogInfoPercent = 0; var template = await _emailService.GetEmailTemplate(jobDetails.EmailTemplateId); progress.Report(new JobStatus { PercentComplete = 0, Title = $"Sending email: {template.Description}", Status = $"Preparing to email {subscribed.Count} participants...", Error = false }); var siteId = GetCurrentSiteId(); while (subscribed.Count > 0) { Thread.Sleep(1000); foreach (var emailReminder in subscribed) { if (problemEmails.Contains(emailReminder.Email)) { emailsSkipped++; continue; } bool clearToSend = true; if (!jobDetails.SendToParticipantsToo) { var isParticipant = await _userService .IsEmailSubscribedAsync(emailReminder.Email); if (isParticipant) { clearToSend = false; } } if (emailReminder.SentAt != null || !clearToSend) { // send email _logger.LogTrace("Email job {JobId}: skipping email {Count}/{Total} to {Email}: {Message}", jobId, userCounter + 1, subscribedCount, emailReminder.Email, emailReminder.SentAt != null ? " already sent at " + emailReminder.SentAt : " is a subscribed participant"); emailsSkipped++; } else { // send email _logger.LogTrace("Email job {JobId}: sending email {Count}/{Total} to {Email} with template {EmailTemplate}", jobId, userCounter + 1, subscribedCount, emailReminder.Email, jobDetails.EmailTemplateId); // send email to user try { await _emailService .SendBulkAsync(emailReminder, jobDetails.EmailTemplateId, siteId); await _emailReminderService.UpdateSentDateAsync(emailReminder.Id); addSentCounter++; emailsSent++; } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) { _logger.LogError(ex, "Email job {JobId}: Send failed to {UserId} at {Email}: {ErrorMessage}", jobId, emailReminder.Id, emailReminder.Email, ex.Message); if (!problemEmails.Contains(emailReminder.Email)) { problemEmails.Add(emailReminder.Email); } } #pragma warning restore CA1031 // Do not catch general exception types } userCounter++; if (token.IsCancellationRequested) { break; } if (sw.Elapsed.TotalSeconds - elapsedStatus.TotalSeconds > 5 || userCounter == 1) { elapsedStatus = sw.Elapsed; var remaining = TimeSpan .FromMilliseconds(elapsedStatus.TotalMilliseconds / userCounter * (subscribedCount - userCounter)) .ToString(SpanFormat, CultureInfo.InvariantCulture); var status = new JobStatus { PercentComplete = userCounter * 100 / subscribedCount, Status = $"Sent {emailsSent}, skipped {emailsSkipped} of {subscribedCount}; {elapsedStatus.ToString(SpanFormat, CultureInfo.InvariantCulture)}, remaining: {remaining}, problems: {problemEmails.Count}", Error = false }; progress.Report(status); if (sw.Elapsed.TotalSeconds - elapsedUpdateDbStatus.TotalSeconds > 60 || userCounter == 1) { elapsedUpdateDbStatus = sw.Elapsed; if (addSentCounter > 0) { await _emailService.UpdateSentCount(template.Id, addSentCounter); addSentCounter = 0; } var dbStatusText = string.Format(CultureInfo.InvariantCulture, "{0}%: {1}", status.PercentComplete, status.Status); await _jobRepository.UpdateStatusAsync(jobId, dbStatusText.Substring(0, Math.Min(dbStatusText.Length, 255))); } if (sw.Elapsed.TotalSeconds - elapsedLogInfoStatus.TotalSeconds > 500 || userCounter == 1 || status.PercentComplete - elapsedLogInfoPercent >= 20) { elapsedLogInfoStatus = sw.Elapsed; elapsedLogInfoPercent = status.PercentComplete ?? 0; _logger.LogInformation("Email job {JobId}: {EmailsSent} sent, {EmailsSkipped} skipped of {SubscribedCount} total in {ElapsedTime}, remaining: {EmailsRemaining}, problems: {EmailProblems}", jobId, emailsSent, emailsSkipped, subscribedCount, elapsedStatus.ToString(SpanFormat, CultureInfo.InvariantCulture), remaining, problemEmails.Count); } } } if (token.IsCancellationRequested) { break; } filter.Skip = userCounter; subscribed = await _emailReminderService.GetSubscribersAsync(filter); } template.EmailsSent = emailsSent; await _emailTemplateRepository.UpdateSaveNoAuditAsync(template); string taskStatus = token.IsCancellationRequested ? "Cancelled after" : "Task completed with"; var finalStatus = new JobStatus { PercentComplete = userCounter * 100 / subscribedCount, Status = $"{taskStatus} {emailsSent} sent, {emailsSkipped} skipped of {subscribedCount} in {elapsedStatus.ToString(SpanFormat, CultureInfo.InvariantCulture)}." }; var statusText = string.Format(CultureInfo.InvariantCulture, "{0}%: {1}", finalStatus.PercentComplete, finalStatus.Status); await _jobRepository.UpdateStatusAsync(jobId, statusText.Substring(0, Math.Min(statusText.Length, 255))); _logger.LogInformation("Email job {JobId}: " + taskStatus + " {EmailsSent} sent, {EmailsSkipped} skipped of {SubscribedCount} total in {ElapsedTime}", jobId, emailsSent, emailsSkipped, subscribedCount, elapsedStatus.ToString(SpanFormat, CultureInfo.InvariantCulture)); return(finalStatus); } else { _logger.LogWarning("User {UserId} attempted to send bulk emails in job {JobId} with no subscribed participants.", userId, jobId); await _jobRepository.UpdateStatusAsync(jobId, "No participants were subscribed."); return(new JobStatus { PercentComplete = 0, Status = "No participants are subscribed.", Error = true }); } }