mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 14:04:26 +01:00
Refactor: Move email services to the email plugin directory
This commit is contained in:
@@ -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<T>(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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -94,6 +94,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Plugins\Emails\Mails\" />
|
||||
<Folder Include="wwwroot\vendor\bootstrap" />
|
||||
<Folder Include="wwwroot\vendor\clipboard.js\" />
|
||||
<Folder Include="wwwroot\vendor\highlightjs\" />
|
||||
|
||||
@@ -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<ServerEmailSettingsData>();
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Services.Mails.EmailSettings>();
|
||||
var serverEmailSettings = await _Settings.GetSettingAsync<EmailSettings>();
|
||||
if (serverEmailSettings?.Server is String server)
|
||||
{
|
||||
serverEmailSettings.DisableCertificateCheck = Extensions.IsLocalNetwork(server);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
41
BTCPayServer/Plugins/Emails/Services/EmailSender.cs
Normal file
41
BTCPayServer/Plugins/Emails/Services/EmailSender.cs
Normal 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();
|
||||
}
|
||||
39
BTCPayServer/Plugins/Emails/Services/EmailSenderFactory.cs
Normal file
39
BTCPayServer/Plugins/Emails/Services/EmailSenderFactory.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
125
BTCPayServer/Plugins/Emails/Services/EmailSettings.cs
Normal file
125
BTCPayServer/Plugins/Emails/Services/EmailSettings.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
12
BTCPayServer/Plugins/Emails/Services/IEmailSender.cs
Normal file
12
BTCPayServer/Plugins/Emails/Services/IEmailSender.cs
Normal 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();
|
||||
}
|
||||
15
BTCPayServer/Plugins/Emails/Services/ServerEmailSender.cs
Normal file
15
BTCPayServer/Plugins/Emails/Services/ServerEmailSender.cs
Normal 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>();
|
||||
}
|
||||
48
BTCPayServer/Plugins/Emails/Services/StoreEmailSender.cs
Normal file
48
BTCPayServer/Plugins/Emails/Services/StoreEmailSender.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@{
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user