-
Notifications
You must be signed in to change notification settings - Fork 806
Background Tasks Bulk Email Sending
Victor Tomaili edited this page May 3, 2021
·
1 revision
- We need to create a background service to run on separate thread to send email from QueuedEmail table
- We will configure different Email Account to send email for Application in EmailAccounts table, e.g
- We will send newsletter to our customers via [email protected]
- We will send order details via [email protected]
- We will store all outgoing email entries to QueuedEmail Tables for sending via our background task
public class DefaultDB_20170130_154500_Emails : AutoReversingMigration
{
public override void Up()
{
this.CreateTableWithId32("EmailAccounts", "Id", s => s
.WithColumn("Email").AsString(255).NotNullable()
.WithColumn("DisplayName").AsString(255).NotNullable()
.WithColumn("Host").AsString(255).NotNullable()
.WithColumn("Port").AsInt32().NotNullable()
.WithColumn("Username").AsString(255).NotNullable()
.WithColumn("Password").AsString(255).NotNullable()
.WithColumn("EnableSsl").AsBoolean().NotNullable()
.WithColumn("UseDefaultCredentials").AsBoolean().NotNullable()
.WithColumn("InsertDate").AsDateTime().NotNullable()
.WithColumn("InsertUserId").AsInt32().NotNullable()
.WithColumn("UpdateDate").AsDateTime().Nullable()
.WithColumn("UpdateUserId").AsInt32().Nullable()
.WithColumn("IsActive").AsInt16().NotNullable().WithDefaultValue(1));
this.CreateTableWithId32("QueuedEmail", "Id", s => s
.WithColumn("PriorityId").AsInt32().NotNullable()
.WithColumn("From").AsString(500).NotNullable()
.WithColumn("FromName").AsString(500).Nullable()
.WithColumn("To").AsString(500).NotNullable()
.WithColumn("ToName").AsString(500).Nullable()
.WithColumn("ReplyTo").AsString(500).Nullable()
.WithColumn("ReplyToName").AsString(500).Nullable()
.WithColumn("CC").AsString(500).Nullable()
.WithColumn("Bcc").AsString(500).Nullable()
.WithColumn("Subject").AsString(1000).Nullable()
.WithColumn("Body").AsString(int.MaxValue).Nullable()
.WithColumn("AttachmentFilePath").AsString(1000).Nullable()
.WithColumn("AttachmentFileName").AsString(1000).Nullable()
.WithColumn("AttachedDownloadId").AsInt32().Nullable()
.WithColumn("CreatedOnUtc").AsDateTime().NotNullable()
.WithColumn("SentTries").AsInt32().Nullable()
.WithColumn("SentOnUtc").AsDateTime().Nullable()
.WithColumn("EmailAccountId").AsInt32().NotNullable()
.ForeignKey("FK_QueuedEmail_EmailAccountId", "EmailAccounts", "Id")
.WithColumn("DontSendBeforeDateUtc").AsDateTime().Nullable()
.WithColumn("HasError").AsBoolean().Nullable()
.WithColumn("Result").AsString(1000).Nullable());
}
}
public static class EmailThread
{
private static Thread _SendEmailThread = null;
public static void StartEmailThread()
{
if (_SendEmailThread == null)
{
_SendEmailThread = new Thread(new ThreadStart(SendEmails));
_SendEmailThread.Priority = ThreadPriority.Lowest;
_SendEmailThread.Start();
}
}
public static void EndEmailThread()
{
if (_SendEmailThread != null)
{
_SendEmailThread.Abort();
}
}
private static void SendEmails()
{
bool enableEmailService = Convert.ToBoolean(ConfigurationManager.AppSettings["EnableEmailService"]);
if (enableEmailService)
{
while (true)
{
Thread.Sleep(60000); // 10 sec
var connection = SqlConnections.NewFor<MyRow>();
var request = new ListRequest();
ListResponse<MyRow> rows =
new MyRepository().List(connection, request);
if (rows.TotalCount == 0)
break;
foreach (MyRow row in rows.Entities)
{
try
{
MailMessage mailMessage = new MailMessage();
mailMessage.From = new MailAddress(row.From, row.FromName);
foreach (var to in row.To.Split(','))
mailMessage.To.Add(new MailAddress(to));
if (!string.IsNullOrEmpty(row.Cc))
foreach (var cc in row.Cc.Split(','))
mailMessage.CC.Add(new MailAddress(cc));
if (!string.IsNullOrEmpty(row.Bcc))
foreach (var bcc in row.Bcc.Split(','))
mailMessage.Bcc.Add(new MailAddress(bcc));
mailMessage.Subject = row.Subject;
mailMessage.Body = row.Body;
mailMessage.IsBodyHtml = true;
EmailAccountsRow emailAccount = connection.TryFirst<EmailAccountsRow>(EmailAccountsRow.Fields.Id == (int)row.EmailAccountId.Value);
SmtpClient smtp = new SmtpClient
{
Host = emailAccount.Host,
Port = (int)emailAccount.Port,
EnableSsl = (bool)emailAccount.EnableSsl,
DeliveryMethod = SmtpDeliveryMethod.Network,
Credentials = new System.Net.NetworkCredential(emailAccount.Username, emailAccount.Password),
Timeout = 30000,
};
smtp.Send(mailMessage);
row.HasError = false;
row.SentTries = 1;
row.SentOnUtc = DateTime.UtcNow;
}
catch (Exception ex)
{
row.HasError = true;
row.Result = ex.Message;
row.SentTries = 1;
}
connection.UpdateById<MyRow>(row);
}
}
}
}
}
- Since In a separate thread, as there is no http request, user is NULL
- We need to impersonate user which has rights to services (in our case EmailAccountsRow/Repository and QueuedEmailRow/Repository)
- We need register Serenity's ImpersonatingAuthenticationService to make it work
var registrar = Dependency.Resolve<IDependencyRegistrar>();
//.....
registrar.RegisterInstance<IAuthorizationService>(new ImpersonatingAuthorizationService(new Administration.AuthorizationService()));
(Dependency.Resolve<IAuthorizationService>() as ImpersonatingAuthorizationService).Impersonate("admin");
//... code to run under impersonated user
(Dependency.Resolve<IAuthorizationService>() as ImpersonatingAuthorizationService).UndoImpersonate();
##Working Code for EmailThread.cs
namespace Serene
{
using Administration.Entities;
using Serenity;
using Serenity.Abstractions;
using Serenity.Data;
using Serenity.Services;
using Serenity.Web;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Net.Mail;
using System.Threading;
using MyRepository = Administration.Repositories.QueuedEmailRepository;
using MyRow = Administration.Entities.QueuedEmailRow;
public static class EmailThread
{
private static Thread _SendEmailThread = null;
public static void StartEmailThread()
{
if (_SendEmailThread == null)
{
_SendEmailThread = new Thread(new ThreadStart(SendEmails));
_SendEmailThread.Priority = ThreadPriority.Lowest;
_SendEmailThread.Start();
}
}
public static void EndEmailThread()
{
if (_SendEmailThread != null)
{
_SendEmailThread.Abort();
}
}
private static void SendEmails()
{
bool enableEmailService = Convert.ToBoolean(ConfigurationManager.AppSettings["EnableEmailService"]);
if (enableEmailService)
{
(Dependency.Resolve<IAuthorizationService>() as ImpersonatingAuthorizationService).Impersonate("admin");
while (true)
{
Thread.Sleep(60000); // 10 sec
var connection = SqlConnections.NewFor<MyRow>();
var request = new ListRequest();
ListResponse<MyRow> rows =
new MyRepository().List(connection, request);
if (rows.TotalCount == 0)
break;
foreach (MyRow row in rows.Entities)
{
try
{
MailMessage mailMessage = new MailMessage();
mailMessage.From = new MailAddress(row.From, row.FromName);
foreach (var to in row.To.Split(','))
mailMessage.To.Add(new MailAddress(to));
if (!string.IsNullOrEmpty(row.Cc))
foreach (var cc in row.Cc.Split(','))
mailMessage.CC.Add(new MailAddress(cc));
if (!string.IsNullOrEmpty(row.Bcc))
foreach (var bcc in row.Bcc.Split(','))
mailMessage.Bcc.Add(new MailAddress(bcc));
mailMessage.Subject = row.Subject;
mailMessage.Body = row.Body;
mailMessage.IsBodyHtml = true;
EmailAccountsRow emailAccount = connection.TryFirst<EmailAccountsRow>(EmailAccountsRow.Fields.Id == (int)row.EmailAccountId.Value);
SmtpClient smtp = new SmtpClient
{
Host = emailAccount.Host,
Port = (int)emailAccount.Port,
EnableSsl = (bool)emailAccount.EnableSsl,
DeliveryMethod = SmtpDeliveryMethod.Network,
Credentials = new System.Net.NetworkCredential(emailAccount.Username, emailAccount.Password),
Timeout = 30000,
};
smtp.Send(mailMessage);
row.HasError = false;
row.SentTries = 1;
row.SentOnUtc = DateTime.UtcNow;
}
catch (Exception ex)
{
row.HasError = true;
row.Result = ex.Message;
row.SentTries = 1;
}
connection.UpdateById<MyRow>(row);
}
}
(Dependency.Resolve<IAuthorizationService>() as ImpersonatingAuthorizationService).UndoImpersonate();
}
}
}
}
Copyright © Serenity Platform 2017-present. All rights reserved.
Documentation | Serene Template | Live Demo | Premium Support | Issues | Discussions