From 68cf9a4604d2353e1474f70286afa35c14555354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Keresztury?= Date: Sun, 24 Apr 2022 19:24:21 +0200 Subject: [PATCH 1/2] MessageId improvements for MailKitSender Use local MessageId as SendResponse.MessageId Use remotely generated MessageId when using Amazon SES --- .../FluentEmail.MailKit/MailKitSender.cs | 54 ++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/src/Senders/FluentEmail.MailKit/MailKitSender.cs b/src/Senders/FluentEmail.MailKit/MailKitSender.cs index 8a5d1413..c78e36cf 100644 --- a/src/Senders/FluentEmail.MailKit/MailKitSender.cs +++ b/src/Senders/FluentEmail.MailKit/MailKitSender.cs @@ -1,11 +1,13 @@ using FluentEmail.Core; using FluentEmail.Core.Interfaces; using FluentEmail.Core.Models; +using MailKit; using MailKit.Net.Smtp; using MimeKit; using System; using System.IO; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -17,6 +19,7 @@ namespace FluentEmail.MailKitSmtp public class MailKitSender : ISender { private readonly SmtpClientOptions _smtpClientOptions; + private readonly bool _isAmazonSes; /// /// Creates a sender that uses the given SmtpClientOptions when sending with MailKit. Since the client is internal this will dispose of the client. @@ -25,6 +28,7 @@ public class MailKitSender : ISender public MailKitSender(SmtpClientOptions smtpClientOptions) { _smtpClientOptions = smtpClientOptions; + _isAmazonSes = smtpClientOptions.Server.EndsWith("amazonaws.com"); } /// @@ -35,8 +39,8 @@ public MailKitSender(SmtpClientOptions smtpClientOptions) /// Cancellation Token. public SendResponse Send(IFluentEmail email, CancellationToken? token = null) { - var response = new SendResponse(); var message = CreateMailMessage(email); + var response = new SendResponse() { MessageId = message.MessageId }; if (token?.IsCancellationRequested ?? false) { @@ -77,8 +81,23 @@ public SendResponse Send(IFluentEmail email, CancellationToken? token = null) client.Authenticate(_smtpClientOptions.User, _smtpClientOptions.Password, token.GetValueOrDefault()); } + var mre = new ManualResetEventSlim(false); + if (_isAmazonSes) + { + // If using Amazon SES, subscribe to the MessageSent event where we can retrieve the overwritten MessageId, then signal ManualResetEventSlim + client.MessageSent += (s, e) => SmtpClient_MessageSent(s, e, mre); + } + else + { + // Otherwise signal ManualResetEventSlim right away + mre.Set(); + } + client.Send(message, token.GetValueOrDefault()); client.Disconnect(true, token.GetValueOrDefault()); + + // Block until ManualResetEventSlim is signaled + mre.Wait(); } } catch (Exception ex) @@ -97,8 +116,8 @@ public SendResponse Send(IFluentEmail email, CancellationToken? token = null) /// Cancellation Token. public async Task SendAsync(IFluentEmail email, CancellationToken? token = null) { - var response = new SendResponse(); var message = CreateMailMessage(email); + var response = new SendResponse() { MessageId = message.MessageId }; if (token?.IsCancellationRequested ?? false) { @@ -139,8 +158,23 @@ await client.ConnectAsync( await client.AuthenticateAsync(_smtpClientOptions.User, _smtpClientOptions.Password, token.GetValueOrDefault()); } + var mre = new ManualResetEventSlim(false); + if (_isAmazonSes) + { + // If using Amazon SES, subscribe to the MessageSent event where we can retrieve the overwritten MessageId, then signal ManualResetEventSlim + client.MessageSent += (s, e) => SmtpClient_MessageSent(s, e, mre); + } + else + { + // Otherwise signal ManualResetEventSlim right away + mre.Set(); + } + await client.SendAsync(message, token.GetValueOrDefault()); await client.DisconnectAsync(true, token.GetValueOrDefault()); + + // Block until ManualResetEventSlim is signaled + mre.Wait(); } } catch (Exception ex) @@ -151,6 +185,22 @@ await client.ConnectAsync( return response; } + private void SmtpClient_MessageSent(object sender, MessageSentEventArgs e, ManualResetEventSlim mre) + { + // Example response format: "Ok 010701805bea386d-8411ef2a-5a8b-46bc-9cbb-585ace484c24-000000" + var match = Regex.Match(e.Response, @"Ok ([0-9a-z\-]+)"); + if (match.Success) + { + // Strip "email-smtp" and similar prefixes from SMTP hostname: https://docs.aws.amazon.com/general/latest/gr/ses.html + var domain = _smtpClientOptions.Server.Substring(_smtpClientOptions.Server.IndexOf('.') + 1); + var id = $"<{match.Groups[1].Value}@{domain}>"; + e.Message.MessageId = id; + } + + // Trigger ManualResetEventSlim to signal that the processing is now complete + mre.Set(); + } + /// /// Saves email to a pickup directory. /// From abbad109ed04af630c5e00a1293d2a075768f731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Keresztury?= Date: Mon, 30 May 2022 18:09:06 +0200 Subject: [PATCH 2/2] Pass messageId as reference --- .../FluentEmail.MailKit/MailKitSender.cs | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/Senders/FluentEmail.MailKit/MailKitSender.cs b/src/Senders/FluentEmail.MailKit/MailKitSender.cs index c78e36cf..72949236 100644 --- a/src/Senders/FluentEmail.MailKit/MailKitSender.cs +++ b/src/Senders/FluentEmail.MailKit/MailKitSender.cs @@ -82,10 +82,12 @@ public SendResponse Send(IFluentEmail email, CancellationToken? token = null) } var mre = new ManualResetEventSlim(false); + string messageId = null; + if (_isAmazonSes) { // If using Amazon SES, subscribe to the MessageSent event where we can retrieve the overwritten MessageId, then signal ManualResetEventSlim - client.MessageSent += (s, e) => SmtpClient_MessageSent(s, e, mre); + client.MessageSent += (s, e) => SmtpClient_MessageSent(s, e, mre, ref messageId); } else { @@ -96,8 +98,14 @@ public SendResponse Send(IFluentEmail email, CancellationToken? token = null) client.Send(message, token.GetValueOrDefault()); client.Disconnect(true, token.GetValueOrDefault()); - // Block until ManualResetEventSlim is signaled + // Block until ManualResetEventSlim is signaled to make sure messageId's value is final mre.Wait(); + + if (messageId != null) + { + // If we were able to parse a MessageId in SmtpClient_MessageSent, use it instead of our own value + response.MessageId = messageId; + } } } catch (Exception ex) @@ -159,10 +167,12 @@ await client.ConnectAsync( } var mre = new ManualResetEventSlim(false); + string messageId = null; + if (_isAmazonSes) { // If using Amazon SES, subscribe to the MessageSent event where we can retrieve the overwritten MessageId, then signal ManualResetEventSlim - client.MessageSent += (s, e) => SmtpClient_MessageSent(s, e, mre); + client.MessageSent += (s, e) => SmtpClient_MessageSent(s, e, mre, ref messageId); } else { @@ -173,8 +183,14 @@ await client.ConnectAsync( await client.SendAsync(message, token.GetValueOrDefault()); await client.DisconnectAsync(true, token.GetValueOrDefault()); - // Block until ManualResetEventSlim is signaled + // Block until ManualResetEventSlim is signaled to make sure messageId's value is final mre.Wait(); + + if (messageId != null) + { + // If we were able to parse a MessageId in SmtpClient_MessageSent, use it instead of our own value + response.MessageId = messageId; + } } } catch (Exception ex) @@ -185,7 +201,7 @@ await client.ConnectAsync( return response; } - private void SmtpClient_MessageSent(object sender, MessageSentEventArgs e, ManualResetEventSlim mre) + private void SmtpClient_MessageSent(object sender, MessageSentEventArgs e, ManualResetEventSlim mre, ref string messageId) { // Example response format: "Ok 010701805bea386d-8411ef2a-5a8b-46bc-9cbb-585ace484c24-000000" var match = Regex.Match(e.Response, @"Ok ([0-9a-z\-]+)"); @@ -194,10 +210,10 @@ private void SmtpClient_MessageSent(object sender, MessageSentEventArgs e, Manua // Strip "email-smtp" and similar prefixes from SMTP hostname: https://docs.aws.amazon.com/general/latest/gr/ses.html var domain = _smtpClientOptions.Server.Substring(_smtpClientOptions.Server.IndexOf('.') + 1); var id = $"<{match.Groups[1].Value}@{domain}>"; - e.Message.MessageId = id; + messageId = id; } - // Trigger ManualResetEventSlim to signal that the processing is now complete + // Trigger ManualResetEventSlim to signal that the processing is now complete and the passed in messageId reference now has a value mre.Set(); }