Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Use MailKit and GreenMail to develop a robust email interaction

5.00/5 (1 vote)
30 Mar 2024CPOL3 min read 4.9K  
In this project, I leverage MailKit to seamlessly manage email communication with a test MailService.
The project aims to develop a robust email interaction system leveraging MailKit for communication with mail servers and GreenMail for efficient testing purposes. By combining these technologies, the project ensures reliable email handling functionality alongside comprehensive testing capabilities.

Introduction

The project aims to develop a robust email interaction system leveraging MailKit for communication with mail servers and GreenMail for efficient testing purposes. 
By combining these technologies, the project ensures reliable email handling functionality alongside comprehensive testing capabilities.

Project Source Code

You can find the project on this github page: GitHub

Key Components

MailKit Integration: Utilizing MailKit, the project establishes a reliable connection with external mail servers, enabling seamless transmission and retrieval of emails. 
MailKit's comprehensive features facilitate smooth communication protocols, including SMTP, IMAP, and POP3, ensuring compatibility with various mail server configurations.

For detailed information on MailKit, visit their website: Official Page

GreenMail for Testing: GreenMail serves as the primary testing environment within the project, providing a lightweight, in-memory mail server solution. Its versatile capabilities allow for the simulation of various email scenarios, including sending, receiving, and manipulation of messages, all within a controlled testing environment. GreenMail's flexibility enables comprehensive testing of email functionalities, ensuring the robustness and reliability of the system.

For detailed information on GreenMail, visit their website: GreenMail Official Page

GreenMail Setup

This setup provides a convenient way to use GreenMail for testing email functionalities within a Docker environment.

To set up GreenMail using a Docker Compose file

greenmail:
  container_name: greenmail
  image: greenmail/standalone
  ports:
    - "3143:3143"
    - "8080:8080"
    - "3025:3025"
  networks:
    - greenmail
  restart: unless-stopped

These setting allows GreenMail will be accessible via the following ports:

  • SMTP: 3143
  • HTTP (for the GreenMail web interface): 8080
  • POP3: 3025

You can now interact with GreenMail for testing purposes. 

Send Mail Flow with MailKit

The MailSenderService class within the Mail.Hub.Domain project encapsulates functionality for sending emails using the MailKit library. This service provides a simple and efficient way to send emails from within a .NET application.

C#
public async Task SendMail(string body)
{
       try
       {
           var message = new MimeMessage();
           message.From.Add(new MailboxAddress("FromName", "fromAddress@gmail.com"));
           message.To.Add(new MailboxAddress("test", "mytestmail@test.it"));
           message.Subject = "test";
           message.Body = new TextPart(MimeKit.Text.TextFormat.Html) { Text = $"<b>{body}</b>" };
           using var client = new SmtpClient();
           client.Connect("localhost", 3025, false);
           client.Authenticate("test", "test");
           await client.SendAsync(message);
           client.Disconnect(true);
       }
       catch (Exception ex)
       {
           logger.LogError(ex, "Not work");
           throw;
       }
}

Code explanation:

  • The SendMail method sends an email with the specified body content.
  • The options required for sending the email, such as sender and recipient addresses, SMTP server details, and authentication credentials, are retrieved from the SenderMailOptions object injected into the service.
  • The client is authenticated using the provided credentials, and the email is sent asynchronously using the SendAsync method.

Receive Mail Flow with MailKit

This ReceiverMailService class encapsulates the functionality to connect to the email server using the IMAP protocol, retrieve new emails, parse them, and handle them accordingly. It utilizes the MailKit library for IMAP operations.

The ParseNewMails method fetches new, unseen emails from the inbox, extracts relevant information such as subject and body, and sends it for further processing using the Mediator pattern.

C#
 public async Task ParseNewMails()
 {
        try
        {
            using var client = new ImapClient();
            client.Connect(_options.Server, _options.Port, false);
            client.Authenticate(_options.UserName, _options.Password);
            _logger.LogInformation("Connected");
            var inbox = client.Inbox;
            inbox.Open(FolderAccess.ReadWrite);
            _logger.LogInformation("Total messages: {0}", inbox.Count);
            _logger.LogInformation("Recent messages: {0}", inbox.Recent);
            var query = SearchQuery.Not(SearchQuery.Seen);
            var messages = await inbox.SearchAsync(query);
            foreach (var item in messages)
            {
                var message = await inbox.GetMessageAsync(item);
                await _mediator.Send(new NewMailCommand() { Title = message.Subject, HtmlBody = message.HtmlBody });
                inbox.AddFlags(item, MessageFlags.Seen, true);
            }
            client.Disconnect(true);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Not work");
            throw;
        }
}

For streamlining communication and promoting decoupling, MediatR serves as an indispensable tool. Learn more about MediatR and its benefits on the official GitHub repository: MediatR GitHub Repository

You can modify  await _mediator.Send(...) line with a private method contains.

Next, let's examine how we integrate this email parsing functionality with Quartz.NET for scheduling:

C#
public class IncomeMailsJob : IJob
{
    private readonly ILogger<IncomeMailsJob> _logger;
    private readonly IReceiverMailService _reviceMailService;
    public IncomeMailsJob(ILogger<IncomeMailsJob> logger, IReceiverMailService reviceMailService)
    {
        _logger = logger;
        _reviceMailService = reviceMailService;
    }

    public async Task Execute(IJobExecutionContext context)
    {
        _logger.LogInformation($"{nameof(IncomeMailsJob)} - Execution Start");
        try
        {
            await _reviceMailService.ParseNewMails();
        }
        catch (Exception e)
        {
            _logger.LogError(e, $"{nameof(IncomeMailsJob)} - Execution Stop");
        }
        _logger.LogInformation($"{nameof(IncomeMailsJob)} - Execution Stop");
    }
}

The IncomeMailsJob class represents a Quartz.NET job responsible for triggering the email parsing process at specified intervals. In the Execute method, it simply invokes the ParseNewMails method of the ReceiverMailService. Any exceptions during execution are logged for debugging purposes.

Finally, let's see how we set up the Quartz.NET job within the application's service configuration.

Generic quarz Job Registration:

C#
public static void AddJobAndTrigger<T>(
   this IServiceCollectionQuartzConfigurator quartz,
   IConfiguration config)
   where T : IJob
    {

        // Use the name of the IJob as the appsettings.json key
        string jobName = typeof(T).Name;
        // Try and load the schedule from configuration
        var cronSchedule = config[jobName];
        // Some minor validation
        if (string.IsNullOrEmpty(cronSchedule))
        {
            throw new Exception($"No Quartz.NET Cron schedule found for job in configuration at {jobName}");
        }

        // register the job as before
        var jobKey = new JobKey(jobName);
        quartz.AddJob<T>(opts => opts.WithIdentity(jobKey));
        quartz.AddTrigger(opts => opts
            .ForJob(jobKey)
            .WithIdentity(jobName + "-trigger")
            .WithCronSchedule(cronSchedule)); // use the schedule from configuration
    }

Using it you can easly add a Quartz job:

C#
services.AddQuartz(q =>
{
    q.AddJobAndTrigger<IncomeMailsJob>(configuration);
});

In this method, we register the necessary services, configure options for email settings, register MediatR for handling commands, configure Quartz.NET to schedule the IncomeMailsJob, and finally start the Quartz.NET scheduler as a hosted service.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)