diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index a7a71d33000f..393023c2d88f 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -46,6 +46,7 @@ using Umbraco.Cms.Infrastructure.HostedServices; using Umbraco.Cms.Infrastructure.Install; using Umbraco.Cms.Infrastructure.Mail; +using Umbraco.Cms.Infrastructure.Mail.Interfaces; using Umbraco.Cms.Infrastructure.Manifest; using Umbraco.Cms.Infrastructure.Migrations; using Umbraco.Cms.Infrastructure.Migrations.Install; @@ -172,14 +173,18 @@ public static IUmbracoBuilder AddCoreInitialServices(this IUmbracoBuilder builde builder.Services.AddSingleton(); + builder.Services.AddTransient(); + // replace builder.Services.AddSingleton( services => new EmailSender( services.GetRequiredService>(), services.GetRequiredService>(), services.GetRequiredService(), + services.GetRequiredService(), services.GetService>(), services.GetService>())); + builder.Services.AddTransient(); builder.Services.AddTransient(); diff --git a/src/Umbraco.Infrastructure/Mail/BasicSmtpEmailSenderClient.cs b/src/Umbraco.Infrastructure/Mail/BasicSmtpEmailSenderClient.cs new file mode 100644 index 000000000000..15ae1d0f4992 --- /dev/null +++ b/src/Umbraco.Infrastructure/Mail/BasicSmtpEmailSenderClient.cs @@ -0,0 +1,50 @@ +using System.Net.Mail; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models.Email; +using Umbraco.Cms.Infrastructure.Extensions; +using Umbraco.Cms.Infrastructure.Mail.Interfaces; +using SecureSocketOptions = MailKit.Security.SecureSocketOptions; +using SmtpClient = MailKit.Net.Smtp.SmtpClient; + +namespace Umbraco.Cms.Infrastructure.Mail +{ + /// + /// A basic SMTP email sender client using MailKits SMTP client. + /// + public class BasicSmtpEmailSenderClient : IEmailSenderClient + { + private readonly GlobalSettings _globalSettings; + public BasicSmtpEmailSenderClient(IOptionsMonitor globalSettings) + { + _globalSettings = globalSettings.CurrentValue; + } + + public async Task SendAsync(EmailMessage message) + { + using var client = new SmtpClient(); + + await client.ConnectAsync( + _globalSettings.Smtp!.Host, + _globalSettings.Smtp.Port, + (SecureSocketOptions)(int)_globalSettings.Smtp.SecureSocketOptions); + + if (!string.IsNullOrWhiteSpace(_globalSettings.Smtp.Username) && + !string.IsNullOrWhiteSpace(_globalSettings.Smtp.Password)) + { + await client.AuthenticateAsync(_globalSettings.Smtp.Username, _globalSettings.Smtp.Password); + } + + var mimeMessage = message.ToMimeMessage(_globalSettings.Smtp!.From); + + if (_globalSettings.Smtp.DeliveryMethod == SmtpDeliveryMethod.Network) + { + await client.SendAsync(mimeMessage); + } + else + { + client.Send(mimeMessage); + } + } + } +} diff --git a/src/Umbraco.Infrastructure/Mail/EmailSender.cs b/src/Umbraco.Infrastructure/Mail/EmailSender.cs index 03b46dbedce9..feb00735f223 100644 --- a/src/Umbraco.Infrastructure/Mail/EmailSender.cs +++ b/src/Umbraco.Infrastructure/Mail/EmailSender.cs @@ -1,20 +1,20 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Net.Mail; using MailKit.Net.Smtp; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MimeKit; using MimeKit.IO; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Mail; using Umbraco.Cms.Core.Models.Email; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Infrastructure.Extensions; -using SecureSocketOptions = MailKit.Security.SecureSocketOptions; -using SmtpClient = MailKit.Net.Smtp.SmtpClient; +using Umbraco.Cms.Infrastructure.Mail.Interfaces; namespace Umbraco.Cms.Infrastructure.Mail; @@ -28,15 +28,18 @@ public class EmailSender : IEmailSender private readonly ILogger _logger; private readonly bool _notificationHandlerRegistered; private GlobalSettings _globalSettings; + private readonly IEmailSenderClient _emailSenderClient; + [Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")] public EmailSender( ILogger logger, IOptionsMonitor globalSettings, IEventAggregator eventAggregator) - : this(logger, globalSettings, eventAggregator, null, null) + : this(logger, globalSettings, eventAggregator,null, null) { } + [Obsolete("Please use the non-obsolete constructor. Will be removed in V17.")] public EmailSender( ILogger logger, IOptionsMonitor globalSettings, @@ -48,6 +51,24 @@ public EmailSender( _eventAggregator = eventAggregator; _globalSettings = globalSettings.CurrentValue; _notificationHandlerRegistered = handler1 is not null || handler2 is not null; + _emailSenderClient = StaticServiceProvider.Instance.GetRequiredService(); + globalSettings.OnChange(x => _globalSettings = x); + } + + [ActivatorUtilitiesConstructor] + public EmailSender( + ILogger logger, + IOptionsMonitor globalSettings, + IEventAggregator eventAggregator, + IEmailSenderClient emailSenderClient, + INotificationHandler? handler1, + INotificationAsyncHandler? handler2) + { + _logger = logger; + _eventAggregator = eventAggregator; + _globalSettings = globalSettings.CurrentValue; + _notificationHandlerRegistered = handler1 is not null || handler2 is not null; + _emailSenderClient = emailSenderClient; globalSettings.OnChange(x => _globalSettings = x); } @@ -152,29 +173,7 @@ private async Task SendAsyncInternal(EmailMessage message, string emailType, boo while (true); } - using var client = new SmtpClient(); - - await client.ConnectAsync( - _globalSettings.Smtp!.Host, - _globalSettings.Smtp.Port, - (SecureSocketOptions)(int)_globalSettings.Smtp.SecureSocketOptions); - - if (!string.IsNullOrWhiteSpace(_globalSettings.Smtp.Username) && - !string.IsNullOrWhiteSpace(_globalSettings.Smtp.Password)) - { - await client.AuthenticateAsync(_globalSettings.Smtp.Username, _globalSettings.Smtp.Password); - } - - var mailMessage = message.ToMimeMessage(_globalSettings.Smtp.From); - if (_globalSettings.Smtp.DeliveryMethod == SmtpDeliveryMethod.Network) - { - await client.SendAsync(mailMessage); - } - else - { - client.Send(mailMessage); - } - - await client.DisconnectAsync(true); + await _emailSenderClient.SendAsync(message); } + } diff --git a/src/Umbraco.Infrastructure/Mail/Interfaces/IEmailSenderClient.cs b/src/Umbraco.Infrastructure/Mail/Interfaces/IEmailSenderClient.cs new file mode 100644 index 000000000000..10dd5284c40f --- /dev/null +++ b/src/Umbraco.Infrastructure/Mail/Interfaces/IEmailSenderClient.cs @@ -0,0 +1,17 @@ +using Umbraco.Cms.Core.Models.Email; + +namespace Umbraco.Cms.Infrastructure.Mail.Interfaces +{ + /// + /// Client for sending an email from a MimeMessage + /// + public interface IEmailSenderClient + { + /// + /// Sends the email message + /// + /// + /// + public Task SendAsync(EmailMessage message); + } +} diff --git a/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs b/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs index 44685878666e..485b7776e21c 100644 --- a/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs +++ b/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs @@ -35,6 +35,7 @@ using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Mail; +using Umbraco.Cms.Infrastructure.Mail.Interfaces; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Mappers; using Umbraco.Cms.Persistence.SqlServer.Services; @@ -80,7 +81,7 @@ public static class TestHelper public static UriUtility UriUtility => s_testHelperInternal.UriUtility; - public static IEmailSender EmailSender { get; } = new EmailSender(new NullLogger(), new TestOptionsMonitor(new GlobalSettings()), Mock.Of()); + public static IEmailSender EmailSender { get; } = new EmailSender(new NullLogger(), new TestOptionsMonitor(new GlobalSettings()), Mock.Of(), Mock.Of(), null,null); public static ITypeFinder GetTypeFinder() => s_testHelperInternal.GetTypeFinder();