Implementing a Worker Service for Newsletter Service in .NET

By Anubhav Kumar — Published: 09-Feb-2026 • Last updated: 09-Feb-2026 19

Sending newsletters is a classic background task. You don’t want email delivery to block HTTP requests, slow down users, or fail silently when traffic spikes. This is where a .NET Worker Service fits perfectly.

In this article, we’ll build a Newsletter Worker Service using .NET that runs independently, processes newsletter jobs in the background, and can be hosted as a Windows Service, Linux daemon, or Docker container.

Why Use a Worker Service for Newsletters?

Sending newsletters inside a controller or API call is risky:

  • Email sending is slow and I/O heavy
  • Failures are hard to retry
  • App restarts can interrupt delivery
  • Scaling becomes painful

A Worker Service solves this by:

  • Running independently of web requests
  • Processing jobs asynchronously
  • Supporting retries, logging, and graceful shutdown
  • Scaling horizontally

What Is a .NET Worker Service?

A Worker Service is a long-running background process built on IHostedService.

Typical use cases:

  • Email & notification sending
  • Queue consumers
  • Scheduled jobs
  • Data sync
  • Report generation

It runs using the Generic Host, same infrastructure used by ASP.NET Core.

Architecture Overview

Simple Newsletter Flow

  • Web App stores newsletter job in DB / Queue
  • Worker Service polls or listens
  • Worker sends emails
  • Status updated (Sent / Failed / Retried)
Web App → Database / Queue → Worker Service → SMTP / Email API

Step 1: Create a Worker Service Project

dotnet new worker -n Newsletter.Worker
cd Newsletter.Worker

Project structure:

Newsletter.Worker
 ├── Program.cs
 ├── Worker.cs
 ├── appsettings.json

Step 2: Configure the Host

Program.cs

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services =>
    {
        services.AddHostedService<Worker>();
        services.AddSingleton<INewsletterService, NewsletterService>();
    })
    .Build();

await host.RunAsync();

Step 3: Implement the Worker

Worker.cs

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;
    private readonly INewsletterService _newsletterService;

    public Worker(
        ILogger<Worker> logger,
        INewsletterService newsletterService)
    {
        _logger = logger;
        _newsletterService = newsletterService;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Newsletter Worker started.");

        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                await _newsletterService.ProcessPendingNewslettersAsync(stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error while processing newsletters");
            }

            await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
        }

        _logger.LogInformation("Newsletter Worker stopping.");
    }
}

Step 4: Newsletter Service Logic

INewsletterService.cs

public interface INewsletterService
{
    Task ProcessPendingNewslettersAsync(CancellationToken token);
}

NewsletterService.cs

public class NewsletterService : INewsletterService
{
    public async Task ProcessPendingNewslettersAsync(CancellationToken token)
    {
        var newsletters = GetPendingNewsletters();

        foreach (var newsletter in newsletters)
        {
            if (token.IsCancellationRequested)
                break;

            try
            {
                await SendEmailAsync(newsletter);
                MarkAsSent(newsletter.Id);
            }
            catch
            {
                MarkAsFailed(newsletter.Id);
            }
        }
    }

    private List<Newsletter> GetPendingNewsletters()
    {
        // Fetch from DB or queue
        return new List<Newsletter>();
    }

    private Task SendEmailAsync(Newsletter newsletter)
    {
        // SMTP / SendGrid / SES logic
        return Task.CompletedTask;
    }

    private void MarkAsSent(int id) { }
    private void MarkAsFailed(int id) { }
}

Step 5: Email Sending Strategy

You can plug in:

  • SMTP (System.Net.Mail)
  • SendGrid
  • Amazon SES
  • Azure Communication Services

Always:

  • Use async APIs
  • Set timeouts
  • Log failures
  • Avoid blocking calls

Step 6: Configuration via appsettings.json

{
  "Worker": {
    "IntervalSeconds": 30
  },
  "Smtp": {
    "Host": "smtp.example.com",
    "Port": 587,
    "Username": "user",
    "Password": "password"
  }
}

Bind configs using IOptions<T> for clean code.

Step 7: Logging & Observability

Use structured logging:

_logger.LogInformation(
    "Sending newsletter {NewsletterId} to {Email}",
    newsletter.Id,
    newsletter.Email);

Recommended:

  • Serilog
  • Seq / ELK
  • Application Insights

Step 8: Graceful Shutdown

Worker services automatically handle shutdown signals:

  • CTRL + C
  • Windows Service stop
  • Docker stop

Using CancellationToken ensures:

  • No half-sent emails
  • Clean exit
  • No data corruption

Step 9: Deployment Options

You can host the worker as:

  • Windows Service
  • Linux systemd service
  • Docker container
  • Kubernetes Job / Deployment
  • Azure App Service (Worker)

Common Enhancements

  • Retry policy (Polly)
  • Queue-based processing (RabbitMQ, Azure Service Bus)
  • Batch sending
  • Rate limiting
  • Dead-letter handling
  • Health checks

When NOT to Use a Worker Service

  • Very small apps with few emails
  • Real-time user notifications
  • One-off scripts
  • In those cases, simpler background jobs may suffice.

Final Thoughts

A .NET Worker Service is a clean, scalable, and production-ready way to handle newsletter delivery. It keeps your web app fast, improves reliability, and gives you full control over retries, failures, and scaling.

Anubhav Kumar
Anubhav Kumar
Student

The Anubhav portal was launched in March 2015 at the behest of the Hon'ble Prime Minister for retiring government officials to leave a record of their experiences while in Govt service .