diff --git a/BTCPayServer.Abstractions/Extensions/ViewsRazor.cs b/BTCPayServer.Abstractions/Extensions/ViewsRazor.cs index a182fa98f..50ac21c97 100644 --- a/BTCPayServer.Abstractions/Extensions/ViewsRazor.cs +++ b/BTCPayServer.Abstractions/Extensions/ViewsRazor.cs @@ -104,11 +104,13 @@ namespace BTCPayServer.Abstractions.Extensions return pages.Any(page => ActivePageClass(viewData, page.ToString(), page.GetType().ToString(), id) == ACTIVE_CLASS); } + [Obsolete("Use IsCategory instead")] public static string ActiveCategoryClass(this ViewDataDictionary viewData, T category, object id = null) { return ActiveCategoryClass(viewData, category.ToString(), id); } + [Obsolete("Use IsCategory instead")] public static string ActiveCategoryClass(this ViewDataDictionary viewData, string category, object id = null) { return IsCategoryActive(viewData, category, id) ? ACTIVE_CLASS : null; diff --git a/BTCPayServer.Tests/BTCPayServerTester.cs b/BTCPayServer.Tests/BTCPayServerTester.cs index 643ba9ad6..a734b15ac 100644 --- a/BTCPayServer.Tests/BTCPayServerTester.cs +++ b/BTCPayServer.Tests/BTCPayServerTester.cs @@ -10,12 +10,10 @@ using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Configuration; -using BTCPayServer.HostedServices; using BTCPayServer.Hosting; using BTCPayServer.Rating; using BTCPayServer.Services; using BTCPayServer.Services.Invoices; -using BTCPayServer.Services.Mails; using BTCPayServer.Services.Rates; using BTCPayServer.Services.Stores; using BTCPayServer.Tests.Logging; @@ -27,7 +25,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using NBitcoin; using NBXplorer; diff --git a/BTCPayServer.Tests/GreenfieldAPITests.cs b/BTCPayServer.Tests/GreenfieldAPITests.cs index 137086ae8..426ef21a3 100644 --- a/BTCPayServer.Tests/GreenfieldAPITests.cs +++ b/BTCPayServer.Tests/GreenfieldAPITests.cs @@ -25,7 +25,7 @@ using BTCPayServer.Rating; using BTCPayServer.Services; using BTCPayServer.Services.Apps; using BTCPayServer.Services.Invoices; -using BTCPayServer.Services.Mails; +using BTCPayServer.Plugins.Emails.Services; using BTCPayServer.Services.Notifications; using BTCPayServer.Services.Notifications.Blobs; using BTCPayServer.Services.Stores; diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index b5210894e..d5a5e30b0 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -46,7 +46,7 @@ using BTCPayServer.Services; using BTCPayServer.Services.Apps; using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Labels; -using BTCPayServer.Services.Mails; +using BTCPayServer.Plugins.Emails.Services; using BTCPayServer.Services.Notifications; using BTCPayServer.Services.Rates; using BTCPayServer.Storage.Models; diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index e504a43da..d7c4d2bbb 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -94,6 +94,7 @@ + diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldServerEmailController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldServerEmailController.cs index c1fd39144..3e73e1d98 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldServerEmailController.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldServerEmailController.cs @@ -1,17 +1,14 @@ #nullable enable -using System; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Client; using BTCPayServer.Client.Models; -using BTCPayServer.Models.ServerViewModels; using BTCPayServer.Services; -using BTCPayServer.Services.Mails; +using BTCPayServer.Plugins.Emails.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json.Linq; namespace BTCPayServer.Controllers.GreenField { @@ -30,7 +27,7 @@ namespace BTCPayServer.Controllers.GreenField _policiesSettings = policiesSettings; _settingsRepository = settingsRepository; } - + private ServerEmailSettingsData ToApiModel(EmailSettings email) { var data = email.ToData(); @@ -52,7 +49,7 @@ namespace BTCPayServer.Controllers.GreenField { if (!string.IsNullOrWhiteSpace(request.From) && !MailboxAddressValidator.IsMailboxAddress(request.From)) ModelState.AddModelError(nameof(request.From), "Invalid email address"); - + if (!ModelState.IsValid) return this.CreateValidationError(ModelState); diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldStoreEmailController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldStoreEmailController.cs index 409e0a8dd..02cc52b64 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldStoreEmailController.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldStoreEmailController.cs @@ -5,7 +5,7 @@ using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Client; using BTCPayServer.Client.Models; using BTCPayServer.Data; -using BTCPayServer.Services.Mails; +using BTCPayServer.Plugins.Emails.Services; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; @@ -72,7 +72,7 @@ namespace BTCPayServer.Controllers.GreenField { if (!string.IsNullOrWhiteSpace(request.From) && !MailboxAddressValidator.IsMailboxAddress(request.From)) ModelState.AddModelError(nameof(request.From), "Invalid email address"); - + if (!ModelState.IsValid) return this.CreateValidationError(ModelState); diff --git a/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs b/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs index 125c7d91e..3c824f3d8 100644 --- a/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs +++ b/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs @@ -15,7 +15,7 @@ using BTCPayServer.Data; using BTCPayServer.Plugins.Webhooks.Controllers; using BTCPayServer.Security; using BTCPayServer.Security.Greenfield; -using BTCPayServer.Services.Mails; +using BTCPayServer.Plugins.Emails.Services; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; diff --git a/BTCPayServer/Controllers/UIAccountController.cs b/BTCPayServer/Controllers/UIAccountController.cs index 8e5cfea42..c15848b88 100644 --- a/BTCPayServer/Controllers/UIAccountController.cs +++ b/BTCPayServer/Controllers/UIAccountController.cs @@ -15,7 +15,7 @@ using BTCPayServer.Filters; using BTCPayServer.Logging; using BTCPayServer.Models.AccountViewModels; using BTCPayServer.Services; -using BTCPayServer.Services.Mails; +using BTCPayServer.Plugins.Emails.Services; using Fido2NetLib; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; diff --git a/BTCPayServer/Controllers/UIManageController.cs b/BTCPayServer/Controllers/UIManageController.cs index b457fcf1c..7c1535d92 100644 --- a/BTCPayServer/Controllers/UIManageController.cs +++ b/BTCPayServer/Controllers/UIManageController.cs @@ -11,7 +11,7 @@ using BTCPayServer.Fido2; using BTCPayServer.Models.ManageViewModels; using BTCPayServer.Security.Greenfield; using BTCPayServer.Services; -using BTCPayServer.Services.Mails; +using BTCPayServer.Plugins.Emails.Services; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; diff --git a/BTCPayServer/Controllers/UIServerController.Users.cs b/BTCPayServer/Controllers/UIServerController.Users.cs index 538c50437..17de893cd 100644 --- a/BTCPayServer/Controllers/UIServerController.Users.cs +++ b/BTCPayServer/Controllers/UIServerController.Users.cs @@ -8,8 +8,6 @@ using BTCPayServer.Abstractions.Models; using BTCPayServer.Data; using BTCPayServer.Events; using BTCPayServer.Models.ServerViewModels; -using BTCPayServer.Services; -using BTCPayServer.Services.Mails; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; diff --git a/BTCPayServer/Controllers/UIServerController.cs b/BTCPayServer/Controllers/UIServerController.cs index 620843221..3abc104b6 100644 --- a/BTCPayServer/Controllers/UIServerController.cs +++ b/BTCPayServer/Controllers/UIServerController.cs @@ -21,7 +21,7 @@ using BTCPayServer.Models.ServerViewModels; using BTCPayServer.Models.StoreViewModels; using BTCPayServer.Services; using BTCPayServer.Services.Apps; -using BTCPayServer.Services.Mails; +using BTCPayServer.Plugins.Emails.Services; using BTCPayServer.Services.Stores; using BTCPayServer.Storage.Services; using BTCPayServer.Storage.Services.Providers; diff --git a/BTCPayServer/Controllers/UIStoresController.Users.cs b/BTCPayServer/Controllers/UIStoresController.Users.cs index b2951ab25..afbaa8f65 100644 --- a/BTCPayServer/Controllers/UIStoresController.Users.cs +++ b/BTCPayServer/Controllers/UIStoresController.Users.cs @@ -10,12 +10,9 @@ using BTCPayServer.Data; using BTCPayServer.Events; using BTCPayServer.Models.StoreViewModels; using BTCPayServer.Security; -using BTCPayServer.Services; -using BTCPayServer.Services.Mails; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Localization; using NicolasDorier.RateLimits; using static BTCPayServer.Services.Stores.StoreRepository; diff --git a/BTCPayServer/Controllers/UIStoresController.cs b/BTCPayServer/Controllers/UIStoresController.cs index 2615b81c7..32f1ff2c2 100644 --- a/BTCPayServer/Controllers/UIStoresController.cs +++ b/BTCPayServer/Controllers/UIStoresController.cs @@ -11,7 +11,7 @@ using BTCPayServer.Security.Bitpay; using BTCPayServer.Services; using BTCPayServer.Services.Apps; using BTCPayServer.Services.Invoices; -using BTCPayServer.Services.Mails; +using BTCPayServer.Plugins.Emails.Services; using BTCPayServer.Services.Rates; using BTCPayServer.Services.Stores; using BTCPayServer.Services.Wallets; diff --git a/BTCPayServer/Data/StoreBlob.cs b/BTCPayServer/Data/StoreBlob.cs index b85bdcdad..cf1feaede 100644 --- a/BTCPayServer/Data/StoreBlob.cs +++ b/BTCPayServer/Data/StoreBlob.cs @@ -7,8 +7,8 @@ using BTCPayServer.Client.JsonConverters; using BTCPayServer.Client.Models; using BTCPayServer.JsonConverters; using BTCPayServer.Payments; +using BTCPayServer.Plugins.Emails.Services; using BTCPayServer.Rating; -using BTCPayServer.Services.Mails; using Newtonsoft.Json; using Newtonsoft.Json.Linq; diff --git a/BTCPayServer/HostedServices/BitpayIPNSender.cs b/BTCPayServer/HostedServices/BitpayIPNSender.cs index 201a4589d..8bcee33f4 100644 --- a/BTCPayServer/HostedServices/BitpayIPNSender.cs +++ b/BTCPayServer/HostedServices/BitpayIPNSender.cs @@ -5,12 +5,11 @@ using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; -using BTCPayServer.Client.Models; using BTCPayServer.Events; using BTCPayServer.Payments; using BTCPayServer.Services; using BTCPayServer.Services.Invoices; -using BTCPayServer.Services.Mails; +using BTCPayServer.Plugins.Emails.Services; using BTCPayServer.Services.Rates; using BTCPayServer.Services.Stores; using Microsoft.Extensions.Hosting; diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index 2eefa55a1..a5359d7df 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -41,7 +41,7 @@ using BTCPayServer.Services.Apps; using BTCPayServer.Services.Fees; using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Labels; -using BTCPayServer.Services.Mails; +using BTCPayServer.Plugins.Emails.Services; using BTCPayServer.Services.Notifications; using BTCPayServer.Services.Notifications.Blobs; using BTCPayServer.Services.PaymentRequests; diff --git a/BTCPayServer/Hosting/MigrationStartupTask.cs b/BTCPayServer/Hosting/MigrationStartupTask.cs index 683520a18..7dbb1482a 100644 --- a/BTCPayServer/Hosting/MigrationStartupTask.cs +++ b/BTCPayServer/Hosting/MigrationStartupTask.cs @@ -16,6 +16,7 @@ using BTCPayServer.Payments.Bitcoin; using BTCPayServer.Payments.Lightning; using BTCPayServer.Payouts; using BTCPayServer.Plugins.Crowdfund; +using BTCPayServer.Plugins.Emails.Services; using BTCPayServer.Plugins.PointOfSale; using BTCPayServer.Services; using BTCPayServer.Services.Apps; @@ -593,7 +594,7 @@ namespace BTCPayServer.Hosting private async Task MigrateEmailServerDisableTLSCerts() { await using var ctx = _DBContextFactory.CreateContext(); - var serverEmailSettings = await _Settings.GetSettingAsync(); + var serverEmailSettings = await _Settings.GetSettingAsync(); if (serverEmailSettings?.Server is String server) { serverEmailSettings.DisableCertificateCheck = Extensions.IsLocalNetwork(server); diff --git a/BTCPayServer/Plugins/Emails/Controllers/UIEmailControllerBase.cs b/BTCPayServer/Plugins/Emails/Controllers/UIEmailControllerBase.cs index a1761fd47..f7ad1bc2a 100644 --- a/BTCPayServer/Plugins/Emails/Controllers/UIEmailControllerBase.cs +++ b/BTCPayServer/Plugins/Emails/Controllers/UIEmailControllerBase.cs @@ -5,10 +5,8 @@ using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Models; -using BTCPayServer.Client; using BTCPayServer.Plugins.Emails.Views; -using BTCPayServer.Services; -using BTCPayServer.Services.Mails; +using BTCPayServer.Plugins.Emails.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Localization; using MimeKit; diff --git a/BTCPayServer/Plugins/Emails/Controllers/UIEmailRuleControllerBase.cs b/BTCPayServer/Plugins/Emails/Controllers/UIEmailRuleControllerBase.cs index 742ecbdd9..f22dbbbcd 100644 --- a/BTCPayServer/Plugins/Emails/Controllers/UIEmailRuleControllerBase.cs +++ b/BTCPayServer/Plugins/Emails/Controllers/UIEmailRuleControllerBase.cs @@ -10,7 +10,7 @@ using BTCPayServer.Client; using BTCPayServer.Data; using BTCPayServer.Plugins.Emails.Views; using BTCPayServer.Plugins.Emails.Views.Shared; -using BTCPayServer.Services.Mails; +using BTCPayServer.Plugins.Emails.Services; using Dapper; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; diff --git a/BTCPayServer/Plugins/Emails/Controllers/UIServerEmailController.cs b/BTCPayServer/Plugins/Emails/Controllers/UIServerEmailController.cs index 9b2509256..e1e963f09 100644 --- a/BTCPayServer/Plugins/Emails/Controllers/UIServerEmailController.cs +++ b/BTCPayServer/Plugins/Emails/Controllers/UIServerEmailController.cs @@ -1,13 +1,9 @@ -using System; -using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; -using BTCPayServer.Abstractions.Extensions; -using BTCPayServer.Abstractions.Models; using BTCPayServer.Client; using BTCPayServer.Plugins.Emails.Views; using BTCPayServer.Services; -using BTCPayServer.Services.Mails; +using BTCPayServer.Plugins.Emails.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Localization; diff --git a/BTCPayServer/Plugins/Emails/Controllers/UIServerEmailRulesController.cs b/BTCPayServer/Plugins/Emails/Controllers/UIServerEmailRulesController.cs index a8d91b727..4c9d53d0a 100644 --- a/BTCPayServer/Plugins/Emails/Controllers/UIServerEmailRulesController.cs +++ b/BTCPayServer/Plugins/Emails/Controllers/UIServerEmailRulesController.cs @@ -3,18 +3,12 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; -using BTCPayServer.Abstractions.Extensions; -using BTCPayServer.Abstractions.Models; using BTCPayServer.Client; -using BTCPayServer.Controllers; using BTCPayServer.Data; using BTCPayServer.Plugins.Emails.Views; -using BTCPayServer.Plugins.Emails.Views.Shared; -using BTCPayServer.Services.Mails; +using BTCPayServer.Plugins.Emails.Services; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Routing; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Localization; diff --git a/BTCPayServer/Plugins/Emails/Controllers/UIStoreEmailRulesController.cs b/BTCPayServer/Plugins/Emails/Controllers/UIStoreEmailRulesController.cs index f2b4d44ba..fc6dad7b8 100644 --- a/BTCPayServer/Plugins/Emails/Controllers/UIStoreEmailRulesController.cs +++ b/BTCPayServer/Plugins/Emails/Controllers/UIStoreEmailRulesController.cs @@ -6,7 +6,7 @@ using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Client; using BTCPayServer.Data; using BTCPayServer.Plugins.Emails.Views; -using BTCPayServer.Services.Mails; +using BTCPayServer.Plugins.Emails.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; diff --git a/BTCPayServer/Plugins/Emails/Controllers/UIStoresEmailController.cs b/BTCPayServer/Plugins/Emails/Controllers/UIStoresEmailController.cs index 065566c7e..d409a5bc1 100644 --- a/BTCPayServer/Plugins/Emails/Controllers/UIStoresEmailController.cs +++ b/BTCPayServer/Plugins/Emails/Controllers/UIStoresEmailController.cs @@ -1,18 +1,13 @@ -using System; -using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; -using BTCPayServer.Abstractions.Extensions; -using BTCPayServer.Abstractions.Models; using BTCPayServer.Client; using BTCPayServer.Data; using BTCPayServer.Plugins.Emails.Views; -using BTCPayServer.Services.Mails; +using BTCPayServer.Plugins.Emails.Services; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Localization; -using MimeKit; namespace BTCPayServer.Plugins.Emails.Controllers; diff --git a/BTCPayServer/Plugins/Emails/HostedServices/EmailRuleProcessorSender.cs b/BTCPayServer/Plugins/Emails/HostedServices/EmailRuleProcessorSender.cs index 84059eda2..184b1261a 100644 --- a/BTCPayServer/Plugins/Emails/HostedServices/EmailRuleProcessorSender.cs +++ b/BTCPayServer/Plugins/Emails/HostedServices/EmailRuleProcessorSender.cs @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Tasks; using BTCPayServer.Data; using BTCPayServer.HostedServices; -using BTCPayServer.Services.Mails; +using BTCPayServer.Plugins.Emails.Services; using Microsoft.Extensions.Logging; using MimeKit; using Newtonsoft.Json.Linq; diff --git a/BTCPayServer/Plugins/Emails/HostedServices/UserEventTriggerHostedService.cs b/BTCPayServer/Plugins/Emails/HostedServices/UserEventTriggerHostedService.cs index de90f5bb9..60d974d48 100644 --- a/BTCPayServer/Plugins/Emails/HostedServices/UserEventTriggerHostedService.cs +++ b/BTCPayServer/Plugins/Emails/HostedServices/UserEventTriggerHostedService.cs @@ -8,12 +8,9 @@ using BTCPayServer.Events; using BTCPayServer.HostedServices; using BTCPayServer.Logging; using BTCPayServer.Services; -using BTCPayServer.Services.Mails; using BTCPayServer.Services.Notifications; using BTCPayServer.Services.Notifications.Blobs; -using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using QRCoder; diff --git a/BTCPayServer/Plugins/Emails/Services/EmailSender.cs b/BTCPayServer/Plugins/Emails/Services/EmailSender.cs new file mode 100644 index 000000000..8b2c7b97a --- /dev/null +++ b/BTCPayServer/Plugins/Emails/Services/EmailSender.cs @@ -0,0 +1,41 @@ +#nullable enable +using System; +using System.Threading.Tasks; +using BTCPayServer.Logging; +using BTCPayServer.Services; +using Microsoft.Extensions.Logging; +using MimeKit; + +namespace BTCPayServer.Plugins.Emails.Services; + +public abstract class EmailSender(IBackgroundJobClient jobClient, EventAggregator eventAggregator, Logs logs) : IEmailSender +{ + public EventAggregator EventAggregator { get; } = eventAggregator; + public Logs Logs { get; } = logs; + + public void SendEmail(MailboxAddress email, string subject, string message) + { + SendEmail(new[] { email }, Array.Empty(), Array.Empty(), subject, message); + } + + public void SendEmail(MailboxAddress[] email, MailboxAddress[] cc, MailboxAddress[] bcc, string subject, string message) + { + jobClient.Schedule(async cancellationToken => + { + var emailSettings = await GetEmailSettings(); + if (emailSettings?.IsComplete() is not true) + { + Logs.Configuration.LogWarning("Should have sent email, but email settings are not configured"); + return; + } + + using var smtp = await emailSettings.CreateSmtpClient(); + var mail = emailSettings.CreateMailMessage(email, cc, bcc, subject, message, true); + var response = await smtp.SendAsync(mail, cancellationToken); + await smtp.DisconnectAsync(true, cancellationToken); + EventAggregator.Publish(new Events.EmailSentEvent(response, mail)); + }, TimeSpan.Zero); + } + + public abstract Task GetEmailSettings(); +} diff --git a/BTCPayServer/Plugins/Emails/Services/EmailSenderFactory.cs b/BTCPayServer/Plugins/Emails/Services/EmailSenderFactory.cs new file mode 100644 index 000000000..45d8996f8 --- /dev/null +++ b/BTCPayServer/Plugins/Emails/Services/EmailSenderFactory.cs @@ -0,0 +1,39 @@ +#nullable enable +using System.Threading.Tasks; +using BTCPayServer.Logging; +using BTCPayServer.Services; +using BTCPayServer.Services.Stores; + +namespace BTCPayServer.Plugins.Emails.Services; + +public class EmailSenderFactory( + IBackgroundJobClient jobClient, + SettingsRepository settingsSettingsRepository, + EventAggregator eventAggregator, + ISettingsAccessor policiesSettings, + StoreRepository storeRepository, + Logs logs) +{ + public Logs Logs { get; } = logs; + + public Task GetEmailSender(string? storeId = null) + { + var serverSender = new ServerEmailSender(settingsSettingsRepository, jobClient, eventAggregator, Logs); + if (string.IsNullOrEmpty(storeId)) + return Task.FromResult(serverSender); + return Task.FromResult(new StoreEmailSender(storeRepository, + !policiesSettings.Settings.DisableStoresToUseServerEmailSettings ? serverSender : null, jobClient, + eventAggregator, storeId, Logs)); + } + + public async Task IsComplete(string? storeId = null) + { + var settings = await this.GetSettings(storeId); + return settings?.IsComplete() is true; + } + public async Task GetSettings(string? storeId = null) + { + var sender = await this.GetEmailSender(storeId); + return await sender.GetEmailSettings(); + } +} diff --git a/BTCPayServer/Plugins/Emails/Services/EmailSettings.cs b/BTCPayServer/Plugins/Emails/Services/EmailSettings.cs new file mode 100644 index 000000000..41a255fd7 --- /dev/null +++ b/BTCPayServer/Plugins/Emails/Services/EmailSettings.cs @@ -0,0 +1,125 @@ +using System.ComponentModel.DataAnnotations; +using System.Threading; +using System.Threading.Tasks; +using BTCPayServer.Client.Models; +using BTCPayServer.Validation; +using MailKit.Net.Smtp; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using MimeKit; +using Newtonsoft.Json; + +namespace BTCPayServer.Plugins.Emails.Services; + +/// +/// Email Settings gets saved to database, and is used by both the server and store settings +/// +public class EmailSettings +{ + public string Server { get; set; } + public int? Port { get; set; } + public string Login { get; set; } + public string Password { get; set; } + public string From { get; set; } + public bool DisableCertificateCheck { get; set; } + [JsonIgnore] + public bool EnabledCertificateCheck + { + get => !DisableCertificateCheck; + set { DisableCertificateCheck = !value; } + } + + // Conversion functions for mapping to and from the client EmailSettingsData model +#nullable enable + public static EmailSettings FromData(EmailSettingsData data, string? existingPassword) + => new() + { + Server = data.Server, + Port = data.Port, + Login = data.Login, + // If login was provided but password was not, use the existing password from database + Password = !string.IsNullOrEmpty(data.Login) && string.IsNullOrEmpty(data.Password) ? + existingPassword : data.Password, + From = data.From, + DisableCertificateCheck = data.DisableCertificateCheck + }; + + public T ToData() where T : EmailSettingsData, new() + => new T() + { + Server = Server, + Port = Port, + Login = Login, + PasswordSet = !string.IsNullOrEmpty(Password), + From = From, + DisableCertificateCheck = DisableCertificateCheck + }; +#nullable restore + + + // + public bool IsComplete() + { + return MailboxAddressValidator.IsMailboxAddress(From) + && !string.IsNullOrWhiteSpace(Server) + && Port != null + && !string.IsNullOrWhiteSpace(From) + && MailboxAddressValidator.IsMailboxAddress(From); + } + + public void Validate(string prefixKey, ModelStateDictionary modelState) + { + if (!string.IsNullOrEmpty(From) && !MailboxAddressValidator.IsMailboxAddress(From)) + { + modelState.AddModelError($"{prefixKey}{nameof(From)}", MailboxAddressAttribute.ErrorMessageConst); + } + } + + public MimeMessage CreateMailMessage(MailboxAddress to, string subject, string message, bool isHtml) => + CreateMailMessage(new[] { to }, null, null, subject, message, isHtml); + public MimeMessage CreateMailMessage(MailboxAddress[] to, MailboxAddress[] cc, MailboxAddress[] bcc, string subject, string message, bool isHtml) + { + var bodyBuilder = new BodyBuilder(); + if (isHtml) + { + bodyBuilder.HtmlBody = message; + } + else + { + bodyBuilder.TextBody = message; + } + + var mm = new MimeMessage(); + mm.Body = bodyBuilder.ToMessageBody(); + mm.Subject = subject; + mm.From.Add(MailboxAddressValidator.Parse(From)); + mm.To.AddRange(to); + mm.Cc.AddRange(cc ?? System.Array.Empty()); + mm.Bcc.AddRange(bcc ?? System.Array.Empty()); + return mm; + } + + public async Task CreateSmtpClient() + { + SmtpClient client = new SmtpClient(); + using var connectCancel = new CancellationTokenSource(10000); + try + { + if (DisableCertificateCheck) + { + client.CheckCertificateRevocation = false; +#pragma warning disable CA5359 // Do Not Disable Certificate Validation + client.ServerCertificateValidationCallback = (s, c, h, e) => true; +#pragma warning restore CA5359 // Do Not Disable Certificate Validation + } + await client.ConnectAsync(Server, Port.Value, MailKit.Security.SecureSocketOptions.Auto, connectCancel.Token); + if ((client.Capabilities & SmtpCapabilities.Authentication) != 0) + await client.AuthenticateAsync(Login ?? string.Empty, Password ?? string.Empty, connectCancel.Token); + } + catch + { + client.Dispose(); + throw; + } + return client; + } +} diff --git a/BTCPayServer/Plugins/Emails/Services/IEmailSender.cs b/BTCPayServer/Plugins/Emails/Services/IEmailSender.cs new file mode 100644 index 000000000..c6a917467 --- /dev/null +++ b/BTCPayServer/Plugins/Emails/Services/IEmailSender.cs @@ -0,0 +1,12 @@ +#nullable enable +using System.Threading.Tasks; +using MimeKit; + +namespace BTCPayServer.Plugins.Emails.Services; + +public interface IEmailSender +{ + void SendEmail(MailboxAddress email, string subject, string message); + void SendEmail(MailboxAddress[] email, MailboxAddress[] cc, MailboxAddress[] bcc, string subject, string message); + Task GetEmailSettings(); +} diff --git a/BTCPayServer/Plugins/Emails/Services/ServerEmailSender.cs b/BTCPayServer/Plugins/Emails/Services/ServerEmailSender.cs new file mode 100644 index 000000000..5081ac39c --- /dev/null +++ b/BTCPayServer/Plugins/Emails/Services/ServerEmailSender.cs @@ -0,0 +1,15 @@ +#nullable enable +using System.Threading.Tasks; +using BTCPayServer.Logging; +using BTCPayServer.Services; + +namespace BTCPayServer.Plugins.Emails.Services; + +class ServerEmailSender(SettingsRepository settingsRepository, + IBackgroundJobClient backgroundJobClient, + EventAggregator eventAggregator, + Logs logs) : EmailSender(backgroundJobClient, eventAggregator, logs) +{ + public override Task GetEmailSettings() + => settingsRepository.GetSettingAsync(); +} diff --git a/BTCPayServer/Plugins/Emails/Services/StoreEmailSender.cs b/BTCPayServer/Plugins/Emails/Services/StoreEmailSender.cs new file mode 100644 index 000000000..28fd8b285 --- /dev/null +++ b/BTCPayServer/Plugins/Emails/Services/StoreEmailSender.cs @@ -0,0 +1,48 @@ +#nullable enable +using System; +using System.Threading.Tasks; +using BTCPayServer.Data; +using BTCPayServer.Logging; +using BTCPayServer.Services; +using BTCPayServer.Services.Stores; + +namespace BTCPayServer.Plugins.Emails.Services; + +class StoreEmailSender( + StoreRepository storeRepository, + EmailSender? fallback, + IBackgroundJobClient backgroundJobClient, + EventAggregator eventAggregator, + string storeId, + Logs logs) + : EmailSender(backgroundJobClient, eventAggregator, logs) +{ + public StoreRepository StoreRepository { get; } = storeRepository; + public EmailSender? FallbackSender { get; } = fallback; + public string StoreId { get; } = storeId ?? throw new ArgumentNullException(nameof(storeId)); + + public override async Task GetEmailSettings() + { + var store = await StoreRepository.FindStore(StoreId); + if (store is null) + return null; + var emailSettings = GetCustomSettings(store); + if (emailSettings is not null) + return emailSettings; + if (FallbackSender is not null) + return await FallbackSender.GetEmailSettings(); + return null; + } + public async Task GetCustomSettings() + { + var store = await StoreRepository.FindStore(StoreId); + if (store is null) + return null; + return GetCustomSettings(store); + } + EmailSettings? GetCustomSettings(StoreData store) + { + var emailSettings = store.GetStoreBlob().EmailSettings; + return emailSettings?.IsComplete() is true ? emailSettings : null; + } +} diff --git a/BTCPayServer/Plugins/Emails/Views/EmailsViewModel.cs b/BTCPayServer/Plugins/Emails/Views/EmailsViewModel.cs index 3a435c9ef..037447009 100644 --- a/BTCPayServer/Plugins/Emails/Views/EmailsViewModel.cs +++ b/BTCPayServer/Plugins/Emails/Views/EmailsViewModel.cs @@ -1,5 +1,5 @@ using System.ComponentModel.DataAnnotations; -using BTCPayServer.Services.Mails; +using BTCPayServer.Plugins.Emails.Services; using BTCPayServer.Validation; namespace BTCPayServer.Plugins.Emails.Views; diff --git a/BTCPayServer/Plugins/Subscriptions/Controllers/UIOfferingController.cs b/BTCPayServer/Plugins/Subscriptions/Controllers/UIOfferingController.cs index 5b69ecb73..30e29d710 100644 --- a/BTCPayServer/Plugins/Subscriptions/Controllers/UIOfferingController.cs +++ b/BTCPayServer/Plugins/Subscriptions/Controllers/UIOfferingController.cs @@ -17,7 +17,7 @@ using BTCPayServer.Plugins.Emails.Views; using BTCPayServer.Services; using BTCPayServer.Services.Apps; using BTCPayServer.Services.Invoices; -using BTCPayServer.Services.Mails; +using BTCPayServer.Plugins.Emails.Services; using BTCPayServer.Views.UIStoreMembership; using Dapper; using Microsoft.AspNetCore.Authorization; diff --git a/BTCPayServer/Plugins/Subscriptions/Views/UISubscriberPortal/SubscriptionsViewModel.cs b/BTCPayServer/Plugins/Subscriptions/Views/UISubscriberPortal/SubscriptionsViewModel.cs index 18fcfb069..5b8e92b50 100644 --- a/BTCPayServer/Plugins/Subscriptions/Views/UISubscriberPortal/SubscriptionsViewModel.cs +++ b/BTCPayServer/Plugins/Subscriptions/Views/UISubscriberPortal/SubscriptionsViewModel.cs @@ -1,13 +1,7 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; +using System.Collections.Generic; using BTCPayServer.Data; using BTCPayServer.Data.Subscriptions; using BTCPayServer.Plugins.Emails.Views; -using BTCPayServer.Services.Mails; -using Microsoft.AspNetCore.Mvc.Razor; -using Microsoft.AspNetCore.Mvc.Rendering; namespace BTCPayServer.Views.UIStoreMembership; diff --git a/BTCPayServer/Services/Mails/EmailSender.cs b/BTCPayServer/Services/Mails/EmailSender.cs deleted file mode 100644 index 4f71794ba..000000000 --- a/BTCPayServer/Services/Mails/EmailSender.cs +++ /dev/null @@ -1,50 +0,0 @@ -#nullable enable -using System; -using System.Threading.Tasks; -using BTCPayServer.Logging; -using Microsoft.Extensions.Logging; -using MimeKit; - -namespace BTCPayServer.Services.Mails -{ - public abstract class EmailSender : IEmailSender - { - public EventAggregator EventAggregator { get; } - public Logs Logs { get; } - - readonly IBackgroundJobClient _JobClient; - - public EmailSender(IBackgroundJobClient jobClient, EventAggregator eventAggregator, Logs logs) - { - EventAggregator = eventAggregator; - Logs = logs; - _JobClient = jobClient ?? throw new ArgumentNullException(nameof(jobClient)); - } - - public void SendEmail(MailboxAddress email, string subject, string message) - { - SendEmail(new[] { email }, Array.Empty(), Array.Empty(), subject, message); - } - - public void SendEmail(MailboxAddress[] email, MailboxAddress[] cc, MailboxAddress[] bcc, string subject, string message) - { - _JobClient.Schedule(async cancellationToken => - { - var emailSettings = await GetEmailSettings(); - if (emailSettings?.IsComplete() is not true) - { - Logs.Configuration.LogWarning("Should have sent email, but email settings are not configured"); - return; - } - - using var smtp = await emailSettings.CreateSmtpClient(); - var mail = emailSettings.CreateMailMessage(email, cc, bcc, subject, message, true); - var response = await smtp.SendAsync(mail, cancellationToken); - await smtp.DisconnectAsync(true, cancellationToken); - EventAggregator.Publish(new Events.EmailSentEvent(response, mail)); - }, TimeSpan.Zero); - } - - public abstract Task GetEmailSettings(); - } -} diff --git a/BTCPayServer/Services/Mails/EmailSenderFactory.cs b/BTCPayServer/Services/Mails/EmailSenderFactory.cs deleted file mode 100644 index f12e1f22c..000000000 --- a/BTCPayServer/Services/Mails/EmailSenderFactory.cs +++ /dev/null @@ -1,55 +0,0 @@ -#nullable enable -using System.Threading.Tasks; -using BTCPayServer.HostedServices; -using BTCPayServer.Logging; -using BTCPayServer.Services.Stores; - -namespace BTCPayServer.Services.Mails -{ - public class EmailSenderFactory - { - public ISettingsAccessor PoliciesSettings { get; } - public Logs Logs { get; } - - private readonly IBackgroundJobClient _jobClient; - private readonly SettingsRepository _settingsRepository; - private readonly EventAggregator _eventAggregator; - private readonly StoreRepository _storeRepository; - - public EmailSenderFactory(IBackgroundJobClient jobClient, - SettingsRepository settingsSettingsRepository, - EventAggregator eventAggregator, - ISettingsAccessor policiesSettings, - StoreRepository storeRepository, - Logs logs) - { - Logs = logs; - _jobClient = jobClient; - _settingsRepository = settingsSettingsRepository; - _eventAggregator = eventAggregator; - PoliciesSettings = policiesSettings; - _storeRepository = storeRepository; - } - - public Task GetEmailSender(string? storeId = null) - { - var serverSender = new ServerEmailSender(_settingsRepository, _jobClient, _eventAggregator, Logs); - if (string.IsNullOrEmpty(storeId)) - return Task.FromResult(serverSender); - return Task.FromResult(new StoreEmailSender(_storeRepository, - !PoliciesSettings.Settings.DisableStoresToUseServerEmailSettings ? serverSender : null, _jobClient, - _eventAggregator, storeId, Logs)); - } - - public async Task IsComplete(string? storeId = null) - { - var settings = await this.GetSettings(storeId); - return settings?.IsComplete() is true; - } - public async Task GetSettings(string? storeId = null) - { - var sender = await this.GetEmailSender(storeId); - return await sender.GetEmailSettings(); - } - } -} diff --git a/BTCPayServer/Services/Mails/EmailSettings.cs b/BTCPayServer/Services/Mails/EmailSettings.cs deleted file mode 100644 index 17671faf3..000000000 --- a/BTCPayServer/Services/Mails/EmailSettings.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.Threading; -using System.Threading.Tasks; -using BTCPayServer.Client.Models; -using BTCPayServer.Validation; -using MailKit.Net.Smtp; -using Microsoft.AspNetCore.Mvc.ModelBinding; -using MimeKit; -using Newtonsoft.Json; - -namespace BTCPayServer.Services.Mails -{ - /// - /// Email Settings gets saved to database, and is used by both the server and store settings - /// - public class EmailSettings - { - public string Server { get; set; } - public int? Port { get; set; } - public string Login { get; set; } - public string Password { get; set; } - public string From { get; set; } - public bool DisableCertificateCheck { get; set; } - [JsonIgnore] - public bool EnabledCertificateCheck - { - get => !DisableCertificateCheck; - set { DisableCertificateCheck = !value; } - } - - // Conversion functions for mapping to and from the client EmailSettingsData model -#nullable enable - public static EmailSettings FromData(EmailSettingsData data, string? existingPassword) - => new() - { - Server = data.Server, - Port = data.Port, - Login = data.Login, - // If login was provided but password was not, use the existing password from database - Password = !string.IsNullOrEmpty(data.Login) && string.IsNullOrEmpty(data.Password) ? - existingPassword : data.Password, - From = data.From, - DisableCertificateCheck = data.DisableCertificateCheck - }; - - public T ToData() where T : EmailSettingsData, new() - => new T() - { - Server = Server, - Port = Port, - Login = Login, - PasswordSet = !string.IsNullOrEmpty(Password), - From = From, - DisableCertificateCheck = DisableCertificateCheck - }; -#nullable restore - - - // - public bool IsComplete() - { - return MailboxAddressValidator.IsMailboxAddress(From) - && !string.IsNullOrWhiteSpace(Server) - && Port != null - && !string.IsNullOrWhiteSpace(From) - && MailboxAddressValidator.IsMailboxAddress(From); - } - - public void Validate(string prefixKey, ModelStateDictionary modelState) - { - if (!string.IsNullOrEmpty(From) && !MailboxAddressValidator.IsMailboxAddress(From)) - { - modelState.AddModelError($"{prefixKey}{nameof(From)}", MailboxAddressAttribute.ErrorMessageConst); - } - } - - public MimeMessage CreateMailMessage(MailboxAddress to, string subject, string message, bool isHtml) => - CreateMailMessage(new[] { to }, null, null, subject, message, isHtml); - public MimeMessage CreateMailMessage(MailboxAddress[] to, MailboxAddress[] cc, MailboxAddress[] bcc, string subject, string message, bool isHtml) - { - var bodyBuilder = new BodyBuilder(); - if (isHtml) - { - bodyBuilder.HtmlBody = message; - } - else - { - bodyBuilder.TextBody = message; - } - - var mm = new MimeMessage(); - mm.Body = bodyBuilder.ToMessageBody(); - mm.Subject = subject; - mm.From.Add(MailboxAddressValidator.Parse(From)); - mm.To.AddRange(to); - mm.Cc.AddRange(cc ?? System.Array.Empty()); - mm.Bcc.AddRange(bcc ?? System.Array.Empty()); - return mm; - } - - public async Task CreateSmtpClient() - { - SmtpClient client = new SmtpClient(); - using var connectCancel = new CancellationTokenSource(10000); - try - { - if (DisableCertificateCheck) - { - client.CheckCertificateRevocation = false; -#pragma warning disable CA5359 // Do Not Disable Certificate Validation - client.ServerCertificateValidationCallback = (s, c, h, e) => true; -#pragma warning restore CA5359 // Do Not Disable Certificate Validation - } - await client.ConnectAsync(Server, Port.Value, MailKit.Security.SecureSocketOptions.Auto, connectCancel.Token); - if ((client.Capabilities & SmtpCapabilities.Authentication) != 0) - await client.AuthenticateAsync(Login ?? string.Empty, Password ?? string.Empty, connectCancel.Token); - } - catch - { - client.Dispose(); - throw; - } - return client; - } - } -} diff --git a/BTCPayServer/Services/Mails/IEmailSender.cs b/BTCPayServer/Services/Mails/IEmailSender.cs deleted file mode 100644 index 7f822d0b5..000000000 --- a/BTCPayServer/Services/Mails/IEmailSender.cs +++ /dev/null @@ -1,13 +0,0 @@ -#nullable enable -using System.Threading.Tasks; -using MimeKit; - -namespace BTCPayServer.Services.Mails -{ - public interface IEmailSender - { - void SendEmail(MailboxAddress email, string subject, string message); - void SendEmail(MailboxAddress[] email, MailboxAddress[] cc, MailboxAddress[] bcc, string subject, string message); - Task GetEmailSettings(); - } -} diff --git a/BTCPayServer/Services/Mails/ServerEmailSender.cs b/BTCPayServer/Services/Mails/ServerEmailSender.cs deleted file mode 100644 index e0afb25b1..000000000 --- a/BTCPayServer/Services/Mails/ServerEmailSender.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Threading.Tasks; -using BTCPayServer.Logging; - -namespace BTCPayServer.Services.Mails -{ - class ServerEmailSender : EmailSender - { - public ServerEmailSender(SettingsRepository settingsRepository, - IBackgroundJobClient backgroundJobClient, - EventAggregator eventAggregator, - Logs logs) : base(backgroundJobClient, eventAggregator, logs) - { - ArgumentNullException.ThrowIfNull(settingsRepository); - SettingsRepository = settingsRepository; - } - - public SettingsRepository SettingsRepository { get; } - - public override Task GetEmailSettings() - { - return SettingsRepository.GetSettingAsync(); - } - } -} diff --git a/BTCPayServer/Services/Mails/StoreEmailSender.cs b/BTCPayServer/Services/Mails/StoreEmailSender.cs deleted file mode 100644 index 167cbf785..000000000 --- a/BTCPayServer/Services/Mails/StoreEmailSender.cs +++ /dev/null @@ -1,53 +0,0 @@ -#nullable enable -using System; -using System.Threading.Tasks; -using BTCPayServer.Data; -using BTCPayServer.Logging; -using BTCPayServer.Services.Stores; - -namespace BTCPayServer.Services.Mails -{ - class StoreEmailSender : EmailSender - { - public StoreEmailSender(StoreRepository storeRepository, - EmailSender? fallback, - IBackgroundJobClient backgroundJobClient, - EventAggregator eventAggregator, - string storeId, - Logs logs) : base(backgroundJobClient, eventAggregator, logs) - { - StoreId = storeId ?? throw new ArgumentNullException(nameof(storeId)); - StoreRepository = storeRepository; - FallbackSender = fallback; - } - - public StoreRepository StoreRepository { get; } - public EmailSender? FallbackSender { get; } - public string StoreId { get; } - - public override async Task GetEmailSettings() - { - var store = await StoreRepository.FindStore(StoreId); - if (store is null) - return null; - var emailSettings = GetCustomSettings(store); - if (emailSettings is not null) - return emailSettings; - if (FallbackSender is not null) - return await FallbackSender.GetEmailSettings(); - return null; - } - public async Task GetCustomSettings() - { - var store = await StoreRepository.FindStore(StoreId); - if (store is null) - return null; - return GetCustomSettings(store); - } - EmailSettings? GetCustomSettings(StoreData store) - { - var emailSettings = store.GetStoreBlob().EmailSettings; - return emailSettings?.IsComplete() is true ? emailSettings : null; - } - } -} diff --git a/BTCPayServer/Views/UIAccount/ForgotPassword.cshtml b/BTCPayServer/Views/UIAccount/ForgotPassword.cshtml index d5f50c3b1..3a8836bcf 100644 --- a/BTCPayServer/Views/UIAccount/ForgotPassword.cshtml +++ b/BTCPayServer/Views/UIAccount/ForgotPassword.cshtml @@ -1,6 +1,4 @@ -@using BTCPayServer.Services -@using BTCPayServer.Services.Mails -@using Microsoft.AspNetCore.Mvc.TagHelpers +@using BTCPayServer.Plugins.Emails.Services @model ForgotPasswordViewModel @inject EmailSenderFactory EmailSenderFactory @{ diff --git a/BTCPayServer/Views/UIServer/Policies.cshtml b/BTCPayServer/Views/UIServer/Policies.cshtml index c9cfd94e3..bb0d06db3 100644 --- a/BTCPayServer/Views/UIServer/Policies.cshtml +++ b/BTCPayServer/Views/UIServer/Policies.cshtml @@ -1,5 +1,5 @@ @using BTCPayServer.Services -@using BTCPayServer.Services.Mails; +@using BTCPayServer.Plugins.Emails.Services @model BTCPayServer.Services.PoliciesSettings @inject EmailSenderFactory EmailSenderFactory @inject TransactionLinkProviders TransactionLinkProviders