Merge pull request #6987 from NicolasDorier/refact-mails

Refactor: Move email services to the email plugin directory
This commit is contained in:
Nicolas Dorier
2025-11-12 11:55:30 +09:00
committed by GitHub
43 changed files with 312 additions and 390 deletions

View File

@@ -104,11 +104,13 @@ namespace BTCPayServer.Abstractions.Extensions
return pages.Any(page => ActivePageClass(viewData, page.ToString(), page.GetType().ToString(), id) == ACTIVE_CLASS); return pages.Any(page => ActivePageClass(viewData, page.ToString(), page.GetType().ToString(), id) == ACTIVE_CLASS);
} }
[Obsolete("Use IsCategory instead")]
public static string ActiveCategoryClass<T>(this ViewDataDictionary viewData, T category, object id = null) public static string ActiveCategoryClass<T>(this ViewDataDictionary viewData, T category, object id = null)
{ {
return ActiveCategoryClass(viewData, category.ToString(), id); return ActiveCategoryClass(viewData, category.ToString(), id);
} }
[Obsolete("Use IsCategory instead")]
public static string ActiveCategoryClass(this ViewDataDictionary viewData, string category, object id = null) public static string ActiveCategoryClass(this ViewDataDictionary viewData, string category, object id = null)
{ {
return IsCategoryActive(viewData, category, id) ? ACTIVE_CLASS : null; return IsCategoryActive(viewData, category, id) ? ACTIVE_CLASS : null;

View File

@@ -10,12 +10,10 @@ using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Configuration; using BTCPayServer.Configuration;
using BTCPayServer.HostedServices;
using BTCPayServer.Hosting; using BTCPayServer.Hosting;
using BTCPayServer.Rating; using BTCPayServer.Rating;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Mails;
using BTCPayServer.Services.Rates; using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using BTCPayServer.Tests.Logging; using BTCPayServer.Tests.Logging;
@@ -27,7 +25,6 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NBitcoin; using NBitcoin;
using NBXplorer; using NBXplorer;

View File

@@ -25,7 +25,7 @@ using BTCPayServer.Rating;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Mails; using BTCPayServer.Plugins.Emails.Services;
using BTCPayServer.Services.Notifications; using BTCPayServer.Services.Notifications;
using BTCPayServer.Services.Notifications.Blobs; using BTCPayServer.Services.Notifications.Blobs;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;

View File

@@ -46,7 +46,7 @@ using BTCPayServer.Services;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Labels; using BTCPayServer.Services.Labels;
using BTCPayServer.Services.Mails; using BTCPayServer.Plugins.Emails.Services;
using BTCPayServer.Services.Notifications; using BTCPayServer.Services.Notifications;
using BTCPayServer.Services.Rates; using BTCPayServer.Services.Rates;
using BTCPayServer.Storage.Models; using BTCPayServer.Storage.Models;

View File

@@ -94,6 +94,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Plugins\Emails\Mails\" />
<Folder Include="wwwroot\vendor\bootstrap" /> <Folder Include="wwwroot\vendor\bootstrap" />
<Folder Include="wwwroot\vendor\clipboard.js\" /> <Folder Include="wwwroot\vendor\clipboard.js\" />
<Folder Include="wwwroot\vendor\highlightjs\" /> <Folder Include="wwwroot\vendor\highlightjs\" />

View File

@@ -1,17 +1,14 @@
#nullable enable #nullable enable
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.Models.ServerViewModels;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Mails; using BTCPayServer.Plugins.Emails.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Controllers.GreenField namespace BTCPayServer.Controllers.GreenField
{ {
@@ -30,7 +27,7 @@ namespace BTCPayServer.Controllers.GreenField
_policiesSettings = policiesSettings; _policiesSettings = policiesSettings;
_settingsRepository = settingsRepository; _settingsRepository = settingsRepository;
} }
private ServerEmailSettingsData ToApiModel(EmailSettings email) private ServerEmailSettingsData ToApiModel(EmailSettings email)
{ {
var data = email.ToData<ServerEmailSettingsData>(); var data = email.ToData<ServerEmailSettingsData>();
@@ -52,7 +49,7 @@ namespace BTCPayServer.Controllers.GreenField
{ {
if (!string.IsNullOrWhiteSpace(request.From) && !MailboxAddressValidator.IsMailboxAddress(request.From)) if (!string.IsNullOrWhiteSpace(request.From) && !MailboxAddressValidator.IsMailboxAddress(request.From))
ModelState.AddModelError(nameof(request.From), "Invalid email address"); ModelState.AddModelError(nameof(request.From), "Invalid email address");
if (!ModelState.IsValid) if (!ModelState.IsValid)
return this.CreateValidationError(ModelState); return this.CreateValidationError(ModelState);

View File

@@ -5,7 +5,7 @@ using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Services.Mails; using BTCPayServer.Plugins.Emails.Services;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Cors;
@@ -72,7 +72,7 @@ namespace BTCPayServer.Controllers.GreenField
{ {
if (!string.IsNullOrWhiteSpace(request.From) && !MailboxAddressValidator.IsMailboxAddress(request.From)) if (!string.IsNullOrWhiteSpace(request.From) && !MailboxAddressValidator.IsMailboxAddress(request.From))
ModelState.AddModelError(nameof(request.From), "Invalid email address"); ModelState.AddModelError(nameof(request.From), "Invalid email address");
if (!ModelState.IsValid) if (!ModelState.IsValid)
return this.CreateValidationError(ModelState); return this.CreateValidationError(ModelState);

View File

@@ -15,7 +15,7 @@ using BTCPayServer.Data;
using BTCPayServer.Plugins.Webhooks.Controllers; using BTCPayServer.Plugins.Webhooks.Controllers;
using BTCPayServer.Security; using BTCPayServer.Security;
using BTCPayServer.Security.Greenfield; using BTCPayServer.Security.Greenfield;
using BTCPayServer.Services.Mails; using BTCPayServer.Plugins.Emails.Services;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;

View File

@@ -15,7 +15,7 @@ using BTCPayServer.Filters;
using BTCPayServer.Logging; using BTCPayServer.Logging;
using BTCPayServer.Models.AccountViewModels; using BTCPayServer.Models.AccountViewModels;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Mails; using BTCPayServer.Plugins.Emails.Services;
using Fido2NetLib; using Fido2NetLib;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;

View File

@@ -11,7 +11,7 @@ using BTCPayServer.Fido2;
using BTCPayServer.Models.ManageViewModels; using BTCPayServer.Models.ManageViewModels;
using BTCPayServer.Security.Greenfield; using BTCPayServer.Security.Greenfield;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Mails; using BTCPayServer.Plugins.Emails.Services;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;

View File

@@ -8,8 +8,6 @@ using BTCPayServer.Abstractions.Models;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Events; using BTCPayServer.Events;
using BTCPayServer.Models.ServerViewModels; using BTCPayServer.Models.ServerViewModels;
using BTCPayServer.Services;
using BTCPayServer.Services.Mails;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;

View File

@@ -21,7 +21,7 @@ using BTCPayServer.Models.ServerViewModels;
using BTCPayServer.Models.StoreViewModels; using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Mails; using BTCPayServer.Plugins.Emails.Services;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using BTCPayServer.Storage.Services; using BTCPayServer.Storage.Services;
using BTCPayServer.Storage.Services.Providers; using BTCPayServer.Storage.Services.Providers;

View File

@@ -10,12 +10,9 @@ using BTCPayServer.Data;
using BTCPayServer.Events; using BTCPayServer.Events;
using BTCPayServer.Models.StoreViewModels; using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Security; using BTCPayServer.Security;
using BTCPayServer.Services;
using BTCPayServer.Services.Mails;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Localization;
using NicolasDorier.RateLimits; using NicolasDorier.RateLimits;
using static BTCPayServer.Services.Stores.StoreRepository; using static BTCPayServer.Services.Stores.StoreRepository;

View File

@@ -11,7 +11,7 @@ using BTCPayServer.Security.Bitpay;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Mails; using BTCPayServer.Plugins.Emails.Services;
using BTCPayServer.Services.Rates; using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets; using BTCPayServer.Services.Wallets;

View File

@@ -7,8 +7,8 @@ using BTCPayServer.Client.JsonConverters;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.JsonConverters; using BTCPayServer.JsonConverters;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Plugins.Emails.Services;
using BTCPayServer.Rating; using BTCPayServer.Rating;
using BTCPayServer.Services.Mails;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;

View File

@@ -5,12 +5,11 @@ using System.Net.Http;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Client.Models;
using BTCPayServer.Events; using BTCPayServer.Events;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Mails; using BTCPayServer.Plugins.Emails.Services;
using BTCPayServer.Services.Rates; using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;

View File

@@ -41,7 +41,7 @@ using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Fees; using BTCPayServer.Services.Fees;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Labels; using BTCPayServer.Services.Labels;
using BTCPayServer.Services.Mails; using BTCPayServer.Plugins.Emails.Services;
using BTCPayServer.Services.Notifications; using BTCPayServer.Services.Notifications;
using BTCPayServer.Services.Notifications.Blobs; using BTCPayServer.Services.Notifications.Blobs;
using BTCPayServer.Services.PaymentRequests; using BTCPayServer.Services.PaymentRequests;

View File

@@ -16,6 +16,7 @@ using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Payments.Lightning; using BTCPayServer.Payments.Lightning;
using BTCPayServer.Payouts; using BTCPayServer.Payouts;
using BTCPayServer.Plugins.Crowdfund; using BTCPayServer.Plugins.Crowdfund;
using BTCPayServer.Plugins.Emails.Services;
using BTCPayServer.Plugins.PointOfSale; using BTCPayServer.Plugins.PointOfSale;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
@@ -593,7 +594,7 @@ namespace BTCPayServer.Hosting
private async Task MigrateEmailServerDisableTLSCerts() private async Task MigrateEmailServerDisableTLSCerts()
{ {
await using var ctx = _DBContextFactory.CreateContext(); await using var ctx = _DBContextFactory.CreateContext();
var serverEmailSettings = await _Settings.GetSettingAsync<Services.Mails.EmailSettings>(); var serverEmailSettings = await _Settings.GetSettingAsync<EmailSettings>();
if (serverEmailSettings?.Server is String server) if (serverEmailSettings?.Server is String server)
{ {
serverEmailSettings.DisableCertificateCheck = Extensions.IsLocalNetwork(server); serverEmailSettings.DisableCertificateCheck = Extensions.IsLocalNetwork(server);

View File

@@ -5,10 +5,8 @@ using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client;
using BTCPayServer.Plugins.Emails.Views; using BTCPayServer.Plugins.Emails.Views;
using BTCPayServer.Services; using BTCPayServer.Plugins.Emails.Services;
using BTCPayServer.Services.Mails;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using MimeKit; using MimeKit;

View File

@@ -10,7 +10,7 @@ using BTCPayServer.Client;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Plugins.Emails.Views; using BTCPayServer.Plugins.Emails.Views;
using BTCPayServer.Plugins.Emails.Views.Shared; using BTCPayServer.Plugins.Emails.Views.Shared;
using BTCPayServer.Services.Mails; using BTCPayServer.Plugins.Emails.Services;
using Dapper; using Dapper;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;

View File

@@ -1,13 +1,9 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Plugins.Emails.Views; using BTCPayServer.Plugins.Emails.Views;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Mails; using BTCPayServer.Plugins.Emails.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;

View File

@@ -3,18 +3,12 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Controllers;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Plugins.Emails.Views; using BTCPayServer.Plugins.Emails.Views;
using BTCPayServer.Plugins.Emails.Views.Shared; using BTCPayServer.Plugins.Emails.Services;
using BTCPayServer.Services.Mails;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;

View File

@@ -6,7 +6,7 @@ using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Plugins.Emails.Views; using BTCPayServer.Plugins.Emails.Views;
using BTCPayServer.Services.Mails; using BTCPayServer.Plugins.Emails.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;

View File

@@ -1,18 +1,13 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Plugins.Emails.Views; using BTCPayServer.Plugins.Emails.Views;
using BTCPayServer.Services.Mails; using BTCPayServer.Plugins.Emails.Services;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using MimeKit;
namespace BTCPayServer.Plugins.Emails.Controllers; namespace BTCPayServer.Plugins.Emails.Controllers;

View File

@@ -7,7 +7,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.HostedServices; using BTCPayServer.HostedServices;
using BTCPayServer.Services.Mails; using BTCPayServer.Plugins.Emails.Services;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MimeKit; using MimeKit;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;

View File

@@ -8,12 +8,9 @@ using BTCPayServer.Events;
using BTCPayServer.HostedServices; using BTCPayServer.HostedServices;
using BTCPayServer.Logging; using BTCPayServer.Logging;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Mails;
using BTCPayServer.Services.Notifications; using BTCPayServer.Services.Notifications;
using BTCPayServer.Services.Notifications.Blobs; using BTCPayServer.Services.Notifications.Blobs;
using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using QRCoder; using QRCoder;

View File

@@ -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<MailboxAddress>(), Array.Empty<MailboxAddress>(), 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<EmailSettings?> GetEmailSettings();
}

View File

@@ -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> policiesSettings,
StoreRepository storeRepository,
Logs logs)
{
public Logs Logs { get; } = logs;
public Task<IEmailSender> GetEmailSender(string? storeId = null)
{
var serverSender = new ServerEmailSender(settingsSettingsRepository, jobClient, eventAggregator, Logs);
if (string.IsNullOrEmpty(storeId))
return Task.FromResult<IEmailSender>(serverSender);
return Task.FromResult<IEmailSender>(new StoreEmailSender(storeRepository,
!policiesSettings.Settings.DisableStoresToUseServerEmailSettings ? serverSender : null, jobClient,
eventAggregator, storeId, Logs));
}
public async Task<bool> IsComplete(string? storeId = null)
{
var settings = await this.GetSettings(storeId);
return settings?.IsComplete() is true;
}
public async Task<EmailSettings?> GetSettings(string? storeId = null)
{
var sender = await this.GetEmailSender(storeId);
return await sender.GetEmailSettings();
}
}

View File

@@ -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;
/// <summary>
/// Email Settings gets saved to database, and is used by both the server and store settings
/// </summary>
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<T>() 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<InternetAddress>());
mm.Bcc.AddRange(bcc ?? System.Array.Empty<InternetAddress>());
return mm;
}
public async Task<SmtpClient> 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;
}
}

View File

@@ -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<EmailSettings?> GetEmailSettings();
}

View File

@@ -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<EmailSettings?> GetEmailSettings()
=> settingsRepository.GetSettingAsync<EmailSettings>();
}

View File

@@ -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<EmailSettings?> 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<EmailSettings?> 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;
}
}

View File

@@ -1,5 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using BTCPayServer.Services.Mails; using BTCPayServer.Plugins.Emails.Services;
using BTCPayServer.Validation; using BTCPayServer.Validation;
namespace BTCPayServer.Plugins.Emails.Views; namespace BTCPayServer.Plugins.Emails.Views;

View File

@@ -17,7 +17,7 @@ using BTCPayServer.Plugins.Emails.Views;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Mails; using BTCPayServer.Plugins.Emails.Services;
using BTCPayServer.Views.UIStoreMembership; using BTCPayServer.Views.UIStoreMembership;
using Dapper; using Dapper;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;

View File

@@ -1,13 +1,7 @@
using System; using System.Collections.Generic;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Data.Subscriptions; using BTCPayServer.Data.Subscriptions;
using BTCPayServer.Plugins.Emails.Views; using BTCPayServer.Plugins.Emails.Views;
using BTCPayServer.Services.Mails;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace BTCPayServer.Views.UIStoreMembership; namespace BTCPayServer.Views.UIStoreMembership;

View File

@@ -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<MailboxAddress>(), Array.Empty<MailboxAddress>(), 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<EmailSettings?> GetEmailSettings();
}
}

View File

@@ -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> 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> policiesSettings,
StoreRepository storeRepository,
Logs logs)
{
Logs = logs;
_jobClient = jobClient;
_settingsRepository = settingsSettingsRepository;
_eventAggregator = eventAggregator;
PoliciesSettings = policiesSettings;
_storeRepository = storeRepository;
}
public Task<IEmailSender> GetEmailSender(string? storeId = null)
{
var serverSender = new ServerEmailSender(_settingsRepository, _jobClient, _eventAggregator, Logs);
if (string.IsNullOrEmpty(storeId))
return Task.FromResult<IEmailSender>(serverSender);
return Task.FromResult<IEmailSender>(new StoreEmailSender(_storeRepository,
!PoliciesSettings.Settings.DisableStoresToUseServerEmailSettings ? serverSender : null, _jobClient,
_eventAggregator, storeId, Logs));
}
public async Task<bool> IsComplete(string? storeId = null)
{
var settings = await this.GetSettings(storeId);
return settings?.IsComplete() is true;
}
public async Task<EmailSettings?> GetSettings(string? storeId = null)
{
var sender = await this.GetEmailSender(storeId);
return await sender.GetEmailSettings();
}
}
}

View File

@@ -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
{
/// <summary>
/// Email Settings gets saved to database, and is used by both the server and store settings
/// </summary>
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<T>() 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<InternetAddress>());
mm.Bcc.AddRange(bcc ?? System.Array.Empty<InternetAddress>());
return mm;
}
public async Task<SmtpClient> 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;
}
}
}

View File

@@ -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<EmailSettings?> GetEmailSettings();
}
}

View File

@@ -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<EmailSettings> GetEmailSettings()
{
return SettingsRepository.GetSettingAsync<EmailSettings>();
}
}
}

View File

@@ -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<EmailSettings?> 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<EmailSettings?> 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;
}
}
}

View File

@@ -1,6 +1,4 @@
@using BTCPayServer.Services @using BTCPayServer.Plugins.Emails.Services
@using BTCPayServer.Services.Mails
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model ForgotPasswordViewModel @model ForgotPasswordViewModel
@inject EmailSenderFactory EmailSenderFactory @inject EmailSenderFactory EmailSenderFactory
@{ @{

View File

@@ -1,5 +1,5 @@
@using BTCPayServer.Services @using BTCPayServer.Services
@using BTCPayServer.Services.Mails; @using BTCPayServer.Plugins.Emails.Services
@model BTCPayServer.Services.PoliciesSettings @model BTCPayServer.Services.PoliciesSettings
@inject EmailSenderFactory EmailSenderFactory @inject EmailSenderFactory EmailSenderFactory
@inject TransactionLinkProviders TransactionLinkProviders @inject TransactionLinkProviders TransactionLinkProviders