From 399d6531e542ee2ba8dad9a1dce118739896a403 Mon Sep 17 00:00:00 2001 From: Eslam Ashraf Date: Thu, 8 Oct 2026 15:09:34 +0300 Subject: [PATCH 1/2] Make MailKitSender.CreateMailMessage protected virtual (upstream #388) Allows subclassing MailKitSender to preprocess the MimeMessage before sending (e.g. DKIM-signing), as requested in https://github.com/lukencode/FluentEmail/issues/388. Adds CanPreprocessMessageByOverridingCreateMailMessage test proving a subclass can modify the outgoing message. --- .../FluentEmail.MailKit/MailKitSender.cs | 6 ++- .../MailKitSmtpSenderTests.cs | 48 +++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/Senders/FluentEmail.MailKit/MailKitSender.cs b/src/Senders/FluentEmail.MailKit/MailKitSender.cs index 8a5d1413..f1ab2a2e 100644 --- a/src/Senders/FluentEmail.MailKit/MailKitSender.cs +++ b/src/Senders/FluentEmail.MailKit/MailKitSender.cs @@ -181,11 +181,13 @@ private async Task SaveToPickupDirectory(MimeMessage message, string pickupDirec } /// - /// Create a MimMessage so MailKit can send it + /// Create a MimeMessage so MailKit can send it. + /// Virtual so subclasses can preprocess the message before sending + /// (e.g. DKIM-sign it) — see https://github.com/lukencode/FluentEmail/issues/388 /// /// The mail message. /// Email data. - private MimeMessage CreateMailMessage(IFluentEmail email) + protected virtual MimeMessage CreateMailMessage(IFluentEmail email) { var data = email.Data; diff --git a/test/FluentEmail.Core.Tests/MailKitSmtpSenderTests.cs b/test/FluentEmail.Core.Tests/MailKitSmtpSenderTests.cs index 7b80921b..2b8f066e 100644 --- a/test/FluentEmail.Core.Tests/MailKitSmtpSenderTests.cs +++ b/test/FluentEmail.Core.Tests/MailKitSmtpSenderTests.cs @@ -1,7 +1,10 @@ using System.IO; +using System.Linq; using System.Threading.Tasks; using FluentEmail.Core; +using FluentEmail.Core.Interfaces; using FluentEmail.MailKitSmtp; +using MimeKit; using NUnit.Framework; using Attachment = FluentEmail.Core.Models.Attachment; @@ -119,5 +122,50 @@ public void CanSendHtmlAndPlaintextTogether() Assert.IsTrue(response.Successful); } + + [Test] + public void CanPreprocessMessageByOverridingCreateMailMessage() + { + var sender = new PreprocessingMailKitSender(new SmtpClientOptions + { + Server = "localhost", + Port = 25, + UseSsl = false, + RequiresAuthentication = false, + UsePickupDirectory = true, + MailPickupDirectory = tempDirectory + }); + Email.DefaultSender = sender; + + var email = Email + .From(fromEmail) + .To(toEmail) + .Subject(subject) + .Body(body); + + var response = email.Send(); + + Assert.IsTrue(response.Successful); + var eml = Directory.EnumerateFiles(tempDirectory, "*.eml").First(); + var content = File.ReadAllText(eml); + Assert.IsTrue(content.Contains("X-Preprocessed: yes"), + "Override of CreateMailMessage should be able to modify the message before sending."); + } + + /// + /// Simulates e.g. DKIM-signing: preprocess the MimeMessage after creation. + /// Possible since CreateMailMessage is protected virtual (issue #388). + /// + private class PreprocessingMailKitSender : MailKitSender + { + public PreprocessingMailKitSender(SmtpClientOptions options) : base(options) { } + + protected override MimeMessage CreateMailMessage(IFluentEmail email) + { + var message = base.CreateMailMessage(email); + message.Headers.Add("X-Preprocessed", "yes"); + return message; + } + } } } From 3efe51ebd0c9e879455bd1d43596459306c596d9 Mon Sep 17 00:00:00 2001 From: SENESO Date: Thu, 8 Oct 2026 19:46:01 +0300 Subject: [PATCH 2/2] Migrate FluentEmail.Graph to Microsoft.Graph 5.x (fixes #380) Microsoft.Graph 3.22.0 pins System.Text.Json to [6.0, 8.0), so the package cannot be installed on .NET 8. Upgrading to 5.105.0 removes that constraint. - Microsoft.Graph.Auth (preview-only, never stable) replaced by Azure.Identity - ClientSecretCredential passed directly to GraphServiceClient - Send call rewritten for the v5 request-builder API - Public API unchanged: same constructor, Send/SendAsync signatures, DI extension - CancellationToken is now actually honored (was silently ignored before) --- .../FluentEmail.Graph.csproj | 5 ++- src/Senders/FluentEmail.Graph/GraphSender.cs | 33 ++++++++----------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/Senders/FluentEmail.Graph/FluentEmail.Graph.csproj b/src/Senders/FluentEmail.Graph/FluentEmail.Graph.csproj index c8695580..847d97e8 100644 --- a/src/Senders/FluentEmail.Graph/FluentEmail.Graph.csproj +++ b/src/Senders/FluentEmail.Graph/FluentEmail.Graph.csproj @@ -9,9 +9,8 @@ - - - + + diff --git a/src/Senders/FluentEmail.Graph/GraphSender.cs b/src/Senders/FluentEmail.Graph/GraphSender.cs index 963ee65a..e5d0bbb2 100644 --- a/src/Senders/FluentEmail.Graph/GraphSender.cs +++ b/src/Senders/FluentEmail.Graph/GraphSender.cs @@ -1,9 +1,10 @@ -using FluentEmail.Core; +using Azure.Identity; +using FluentEmail.Core; using FluentEmail.Core.Interfaces; using FluentEmail.Core.Models; using Microsoft.Graph; -using Microsoft.Graph.Auth; -using Microsoft.Identity.Client; +using Microsoft.Graph.Models; +using Microsoft.Graph.Users.Item.SendMail; using System; using System.Collections.Generic; using System.IO; @@ -19,9 +20,7 @@ public class GraphSender : ISender private readonly string _graphSecret; private bool _saveSent; - private ClientCredentialProvider _authProvider; private GraphServiceClient _graphClient; - private IConfidentialClientApplication _clientApp; public GraphSender( string GraphEmailAppId, @@ -34,15 +33,8 @@ public GraphSender( _graphSecret = GraphEmailSecret; _saveSent = SaveSentItems; - _clientApp = ConfidentialClientApplicationBuilder - .Create(_appId) - .WithTenantId(_tenantId) - .WithClientSecret(_graphSecret) - .Build(); - - _authProvider = new ClientCredentialProvider(_clientApp); - - _graphClient = new GraphServiceClient(_authProvider); + var credential = new ClientSecretCredential(_tenantId, _appId, _graphSecret); + _graphClient = new GraphServiceClient(credential); } public SendResponse Send(IFluentEmail email, CancellationToken? token = null) @@ -77,7 +69,7 @@ public async Task SendAsync(IFluentEmail email, CancellationToken? email.Data.ToAddresses.ForEach(r => toRecipients.Add(new Recipient { EmailAddress = new EmailAddress - { + { Address = r.EmailAddress.ToString(), Name = r.Name } @@ -120,7 +112,7 @@ public async Task SendAsync(IFluentEmail email, CancellationToken? if(email.Data.Attachments != null && email.Data.Attachments.Count > 0) { - message.Attachments = new MessageAttachmentsCollectionPage(); + message.Attachments = new List(); email.Data.Attachments.ForEach(a => { @@ -154,9 +146,12 @@ public async Task SendAsync(IFluentEmail email, CancellationToken? try { await _graphClient.Users[email.Data.FromAddress.EmailAddress] - .SendMail(message, _saveSent) - .Request() - .PostAsync(); + .SendMail + .PostAsync(new SendMailPostRequestBody + { + Message = message, + SaveToSentItems = _saveSent + }, cancellationToken: token ?? CancellationToken.None); return new SendResponse {