This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
##Introduction
In my previous post I detailed how to send emails using FluentEmail and the SMTP Client. However one issue with this is the delay in sending emails. SMTP servers tend to be slow and can take a while to send emails. This can be annoying for users and feel like a logjam in your application.
One way to get around this is to send emails in the background. This way the user can continue using the application without having to wait for the email to send. This is a common pattern in web applications and can be achieved using a background job.
In ASP.NET Core you have two main options (besides more advanced options like Hangfire / Quartz)
In this example I'll use a simple IHostedService to send emails in the background.
The complete source for this is below.
using System.Threading.Tasks.Dataflow;
using Mostlylucid.Email.Models;
namespace Mostlylucid.Email
{
public class EmailSenderHostedService(EmailService emailService, ILogger<EmailSenderHostedService> logger)
: IHostedService, IDisposable
{
private readonly BufferBlock<BaseEmailModel> _mailMessages = new();
private Task _sendTask = Task.CompletedTask;
private CancellationTokenSource cancellationTokenSource = new();
public async Task SendEmailAsync(BaseEmailModel message)
{
await _mailMessages.SendAsync(message);
}
public Task StartAsync(CancellationToken cancellationToken)
{
logger.LogInformation("Starting background e-mail delivery");
// Start the background task
_sendTask = DeliverAsync(cancellationTokenSource.Token);
return Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
logger.LogInformation("Stopping background e-mail delivery");
// Cancel the token to signal the background task to stop
await cancellationTokenSource.CancelAsync();
// Wait until the background task completes or the cancellation token triggers
await Task.WhenAny(_sendTask, Task.Delay(Timeout.Infinite, cancellationToken));
}
private async Task DeliverAsync(CancellationToken token)
{
logger.LogInformation("E-mail background delivery started");
while (!token.IsCancellationRequested)
{
BaseEmailModel? message = null;
try
{if(_mailMessages.Count == 0) continue;
message = await _mailMessages.ReceiveAsync(token);
switch (message)
{
case ContactEmailModel contactEmailModel:
await emailService.SendContactEmail(contactEmailModel);
break;
case CommentEmailModel commentEmailModel:
await emailService.SendCommentEmail(commentEmailModel);
break;
}
logger.LogInformation("Email from {SenderEmail} sent", message.SenderEmail);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception exc)
{
logger.LogError(exc, "Couldn't send an e-mail from {SenderEmail}", message?.SenderEmail);
await Task.Delay(1000, token); // Delay and respect the cancellation token
if (message != null)
{
await _mailMessages.SendAsync(message, token);
}
}
}
logger.LogInformation("E-mail background delivery stopped");
}
public void Dispose()
{
cancellationTokenSource.Cancel();
cancellationTokenSource.Dispose();
}
}
}
Here you can see we handle the starting of the service and setting up a new BufferBlock to hold the emails.
public class EmailSenderHostedService(EmailService emailService, ILogger<EmailSenderHostedService> logger)
: IHostedService, IDisposable
{
private readonly BufferBlock<BaseEmailModel> _mailMessages = new();
private Task _sendTask = Task.CompletedTask;
private CancellationTokenSource cancellationTokenSource = new();
We also set up a new Task to deliver the emails in the background. and a CancellationTokenSource to cancel the task gracefully when we want to stop the service.
We then start up the HostedService with StartAsync and provide the entry point for other services to send an email.
public async Task SendEmailAsync(BaseEmailModel message)
{
await _mailMessages.SendAsync(message);
}
public Task StartAsync(CancellationToken cancellationToken)
{
logger.LogInformation("Starting background e-mail delivery");
// Start the background task
_sendTask = DeliverAsync(cancellationTokenSource.Token);
return Task.CompletedTask;
}
In our Setup class we now need to register the service with the DI container and start the HostedService
services.AddSingleton<EmailSenderHostedService>();
services.AddHostedService(provider => provider.GetRequiredService<EmailSenderHostedService>());
Now we can send emails in the background by calling the SendEmailAsync method on the EmailSenderHostedService. e.g., for the contact form we do this.
var contactModel = new ContactEmailModel()
{
SenderEmail = user.email,
SenderName =user.name,
Comment = commentHtml,
};
await sender.SendEmailAsync(contactModel);
In the code above this adds this message to our BufferBlock<BaseEmailModel>
_mailMessages and the background task will pick it up and send the email.
private async Task DeliverAsync(CancellationToken token)
{
...
while (!token.IsCancellationRequested)
{
BaseEmailModel? message = null;
try
{if(_mailMessages.Count == 0) continue;
message = await _mailMessages.ReceiveAsync(token);
switch (message)
{
case ContactEmailModel contactEmailModel:
await emailService.SendContactEmail(contactEmailModel);
break;
case CommentEmailModel commentEmailModel:
await emailService.SendCommentEmail(commentEmailModel);
break;
}
logger.LogInformation("Email from {SenderEmail} sent", message.SenderEmail);
...
}
logger.LogInformation("E-mail background delivery stopped");
}
This will then loop until we stop the service and continue to monitor the BufferBlock for new emails to send.