We will look at how to build a powerful console application for sending bulk emails with the latest ASP.NET Core 7.0 framework and smtp server. Businesses frequently want a streamlined approach to send emails to a big number of recipients due to the ever-increasing necessity for effective communication. You will learn how to use the features of ASP.NET Core 7.0 to create a dependable bulk email sending solution by following this step-by-step guide.

Step 1: Install Any Necessary Software
Make sure your system has Visual Studio Community 2022 and SQL Server Management Studio (SSMS) installed. You can get them through Microsoft's official websites.

Step 2. Create a New Console Application

  • Navigate to Visual Studio Community 2022.
  • Select "Create a new project."
  • Search for "Console" in the "Create a new project" dialog's search field.
  • Choose "Console App (.NET Core)" as the template.
  • Give your project a name and a location, then click "Create."

3. Launch SQL Server Management Studio.

  • On your computer, launch SQL Server Management Studio.
  • Connect to your SQL Server instance by using the proper server name and authentication method (Windows or SQL Server).

Step 4: Make a New Database.

  • Right-click on "Databases" in the Object Explorer window and select "New Database."
  • In the "New Database" window, in the "Database name" area, provide a name for your database.
  • If necessary, configure other parameters such as data file locations.
  • To build the database, click the "OK" button.

Here's an example

CREATE DATABASE bulkEmailDemoDB;

Step 5. Create a New TableExpand the newly created database in the Object Explorer window.Right-click on "Tables" within your database, and then select "New Table."In the

Table Designer window:
Define the columns for your table. For each column, specify a name, data type, length (if applicable), and other properties.
Click the "Save" icon (or press Ctrl + S) to save the table.
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 ASP.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.