Building a Bulk Email Sending Console App with NET Core 7.0

Introduction

we will dive into the process of creating a powerful console application for sending bulk emails using the latest .NET Core 7.0 framework and smtp server. With the ever-growing need for efficient communication, businesses often require a streamlined method to send emails to a large number of recipients. By following this step-by-step guide, you will learn how to harness the capabilities of .NET Core 7.0 to develop a reliable bulk email sending solution.

Step 1. Install Required Software

Ensure you have Visual Studio Community 2022 and SQL Server Management Studio (SSMS) installed on your system. You can download them from the official Microsoft websites.

Step 2. Create a New Console Application

  1. Open Visual Studio Community 2022.
  2. Click on "Create a new project."
  3. In the "Create a new project" dialog, search for "Console" in the search box.
  4. Select the "Console App (.NET Core)" template.
  5. Choose a name and location for your project, then click "Create."

Step 3. Open SQL Server Management Studio

  1. Launch SQL Server Management Studio on your computer.

  2. Connect to your SQL Server instance by providing the appropriate server name and authentication method (Windows Authentication or SQL Server Authentication).

Step 4. Create a New Database 

  1. In the Object Explorer window, right-click on "Databases" and select "New Database."
  2. In the "New Database" dialog:
  3. Enter a name for your database in the "Database name" field.
  4. Configure other settings like data file locations if needed.
  5. Click the "OK" button to create the database.

Here's an example

CREATE DATABASE bulkEmailDemoDB;

Step 5. Create a New Table

  1. Expand the newly created database in the Object Explorer window.
  2. Right-click on "Tables" within your database, and then select "New Table."
  3. In the Table Designer window:
  4. Define the columns for your table. For each column, specify a name, data type, length (if applicable), and other properties.
  5. Click the "Save" icon (or press Ctrl + S) to save the table.
  6. Provide a name for your table and click the "OK" button to create it.

Here's an example

Choose the table name as per your requirement

CREATE TABLE ClientMailReminder (
    ClientCode VARCHAR(255),
    EmailId VARCHAR(255) NOT NULL,
    PhoneNumber VARCHAR(20) NOT NULL,
    Status VARCHAR(50)
);

Now insert the dummy data for demo purposes. In your table, you can insert genuine record

INSERT INTO ClientMailReminder (ClientCode, EmailId, PhoneNumber, Status)
VALUES
    ('CLNT001', '[email protected]', '1234567890', 'Active'),
    ('CLNT002', '[email protected]', '9876543210', 'Inactive'),
    -- ... (continue for the remaining records)
    ('CLNT020', '[email protected]', '5555555555', 'Active');

Create a model class.

public class ClientMailRemainder
{
    public string? ClientCode { get; set; }
    public string EmailId { get; set; }
    public string PhoneNumber { get; set; }
    public string? Status { get; set; }
}

Let's Install Required NuGet Packages:

  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.SqlServer

Create BulkEmailContext Class:

Next, create a class named BulkEmailContext that inherits from DbContext. This class represents your database context and provides the DbSet properties for your entities.

using Microsoft.EntityFrameworkCore;

public class BulkEmailContext : DbContext
{
    public BulkEmailContext(DbContextOptions<BulkEmailContext> options) : base(options)
    {
    }

    // DbSet properties for your entities
    public DbSet<ClientMailReminder> ClientMailReminders { get; set; }

}

Configure Program.cs (for .NET Core 7.0):

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

namespace Yournamespace;

class Program
{

    static async Task Main(string[] args)
    {
        HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
        
        var options = new DbContextOptionsBuilder<EmailDbContext>().UseSqlServer("your connection string").Options;
        using (var context = new EmailDbContext(options)) // Create your EmailDbContext instance
        {
            await BulkEmail.GetEmailAsync(context); // Call the static method

            Console.WriteLine("Bulk email and mobile sending completed."); // Optional: Print a message
        }



    }
}

Create a BulkEmail.cs file

public static async Task GetEmailAndMobileNumberAsync(EmailDbContext context)
{
    var activeUserEmails = await _context.ClientMailRemainder
    .Where(e => e.Status == "Active")
    .Select(e => e.EmailId)
    .ToListAsync();

    string subject = " DotNet Reminder";
    string content = "<html><body>DotNet meetup reminder</body></html>";
    List<string> emailRecipients = activeUsers.Select(user => user.Email).ToList();
    int counts = context.ClientMailRemainder.Count();
    await SendEmailsInBatchesAsync(counts, emailRecipients, subject, content);
}

Implementing SendEmailsInBatchesAsync method

public static async Task SendEmailsInBatchesAsync(int count,List<string> emailAddresses, string subject, string content, int batchSize = 100)
{
    ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13;

    var smtpClients = new List<SmtpClient>();

    // Determine the total number of rows in the database table
    int totalRowCount = count; // To count table record

    // Calculate the number of batches needed
    int totalBatches = (int)Math.Ceiling((double)totalRowCount / batchSize);

    for (int i = 0; i < totalBatches; i++)
    {
        SmtpClient smtpClient = new SmtpClient("your");
        smtpClient.Port = 587;
        smtpClient.Credentials = new NetworkCredential("your", "your");
        smtpClients.Add(smtpClient);
    }

    var tasks = new List<Task>();

    int emailsSentCount = 0;

    for (int batchIndex = 0; batchIndex < totalBatches; batchIndex++)
    {
        var batch = emailAddresses.Skip(batchIndex * batchSize).Take(batchSize).ToList();
        int startIndex = batchIndex * batchSize;

        tasks.Add(Task.Run(async () =>
        {
            int clientIndex = startIndex / batchSize;

            using (var client = smtpClients[clientIndex])
            {
                for (int j = 0; j < batch.Count; j++)
                {
                    var emailAddress = batch[j];
                    var clientCode = clientCodes[startIndex + j];

                    using (var message = new MailMessage())
                    {
                        message.From = new MailAddress("your");
                        message.Subject = subject;
                        message.Body = content;
                        message.IsBodyHtml = true;
                        message.To.Add(emailAddress);

                        try
                        {
                            await client.SendMailAsync(message);
                            Interlocked.Add(ref emailsSentCount, 1);
                            Console.WriteLine($"Email sent successfully to: {emailAddress}");
                            //emailsSentCount++;
                            Console.WriteLine($"Total emails sent: {emailsSentCount}");
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine($"An error occurred while sending email to {emailAddress}: {ex.Message}");
                        }
                    }
                }
            }
        }));
    }

    await Task.WhenAll(tasks);

    // Dispose all SmtpClient instances
    foreach (var client in smtpClients)
    {
        client.Dispose();
    }
}

The formula (int)Math.Ceiling((double)totalRowCount / batchSize) calculates how many batches you'll need, ensuring you round up to cover all emails.

Loop Through Batches and Initialize SMTP Clients

The code then enters a loop that runs for the total number of batches calculated. Inside the loop, an instance of the SmtpClient class is created and configured for each batch. we are  attempting to distribute the work of sending emails among multiple SMTP clients (perhaps to parallelize the process),

Inside the Task.Run delegate, the code manages sending emails within a batch. It gets the SMTP client based on the batch index, iterates through the batch of email addresses, and sends emails.

  • The Interlocked.Add(ref emailsSentCount, 1); line ensures that the emailsSentCount is incremented in a thread-safe manner, as multiple tasks might be updating it simultaneously.
  • await Task.WhenAll(tasks); waits for all the asynchronous email sending tasks to complete before moving on.

After all the email sending tasks have completed, the code disposes of the SMTP client instances to release resources. 

Conclusion

The creation of the Bulk Email Sending Console App with .NET Core 7.0 has resulted in a robust and efficient solution for managing large-scale email communications. By carefully addressing various aspects of development, such as functionality, architecture, and user interface, the application offers a streamlined approach to sending bulk emails. Leveraging the power of .NET Core 7.0, the architecture was structured to ensure scalability, separation of concerns, and easy integration with external services. Through a well-designed console interface, users can initiate and monitor bulk email sending while receiving timely feedback. The application's handling of concurrency, error management, and security underscores its reliability. By providing comprehensive documentation and incorporating testing practices, the application is poised for seamless adoption and potential future enhancements. Ultimately, this project exemplifies how technological advancements can simplify intricate tasks, paving the way for enhanced communication strategies and business efficiency.