diff --git a/BTCPayServer/Controllers/UIManageController.cs b/BTCPayServer/Controllers/UIManageController.cs
index 01f70ea8e..b457fcf1c 100644
--- a/BTCPayServer/Controllers/UIManageController.cs
+++ b/BTCPayServer/Controllers/UIManageController.cs
@@ -200,7 +200,7 @@ namespace BTCPayServer.Controllers
{
TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Error updating profile"].Value;
}
-
+
return RedirectToAction(nameof(Index));
}
@@ -220,7 +220,7 @@ namespace BTCPayServer.Controllers
}
var callbackUrl = await _callbackGenerator.ForEmailConfirmation(user, Request);
- (await _EmailSenderFactory.GetEmailSender()).SendEmailConfirmation(user.GetMailboxAddress(), callbackUrl);
+ _eventAggregator.Publish(new UserEvent.ConfirmationEmailRequested(user, callbackUrl));
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Verification email sent. Please check your email."].Value;
return RedirectToAction(nameof(Index));
}
diff --git a/BTCPayServer/Controllers/UIServerController.Users.cs b/BTCPayServer/Controllers/UIServerController.Users.cs
index a54de38eb..538c50437 100644
--- a/BTCPayServer/Controllers/UIServerController.Users.cs
+++ b/BTCPayServer/Controllers/UIServerController.Users.cs
@@ -405,9 +405,7 @@ namespace BTCPayServer.Controllers
}
var callbackUrl = await _callbackGenerator.ForEmailConfirmation(user, Request);
-
- (await _emailSenderFactory.GetEmailSender()).SendEmailConfirmation(user.GetMailboxAddress(), callbackUrl);
-
+ _eventAggregator.Publish(new UserEvent.ConfirmationEmailRequested(user, callbackUrl));
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Verification email sent"].Value;
return RedirectToAction(nameof(ListUsers));
}
diff --git a/BTCPayServer/Events/UserEvent.cs b/BTCPayServer/Events/UserEvent.cs
index 6e5554663..71d019331 100644
--- a/BTCPayServer/Events/UserEvent.cs
+++ b/BTCPayServer/Events/UserEvent.cs
@@ -24,6 +24,12 @@ public class UserEvent(ApplicationUser user)
{
public string ResetLink { get; } = resetLink;
}
+
+ public class ConfirmationEmailRequested(ApplicationUser user, string confirmLink) : UserEvent(user)
+ {
+ public string ConfirmLink { get; } = confirmLink;
+ }
+
public class Registered(ApplicationUser user, string approvalLink, string confirmationEmail) : UserEvent(user)
{
public string ApprovalLink { get; } = approvalLink;
diff --git a/BTCPayServer/Extensions/EmailSenderExtensions.cs b/BTCPayServer/Extensions/EmailSenderExtensions.cs
index caa4e9eb6..b8f43cbc6 100644
--- a/BTCPayServer/Extensions/EmailSenderExtensions.cs
+++ b/BTCPayServer/Extensions/EmailSenderExtensions.cs
@@ -9,56 +9,16 @@ namespace BTCPayServer.Services
{
private static string BODY_STYLE = "font-family: Open Sans, Helvetica Neue,Arial,sans-serif; font-color: #292929;";
private static string HEADER_HTML = "
BTCPay Server
";
- private static string BUTTON_HTML = "{button_description}";
-
- private static string CallToAction(string actionName, string actionLink)
- {
- var button = $"{BUTTON_HTML}".Replace("{button_description}", actionName, System.StringComparison.InvariantCulture);
- return button.Replace("{button_link}", HtmlEncoder.Default.Encode(actionLink), System.StringComparison.InvariantCulture);
- }
private static string CreateEmailBody(string body)
{
return $"{HEADER_HTML}{body}";
}
- public static void SendEmailConfirmation(this IEmailSender emailSender, MailboxAddress address, string link)
- {
- emailSender.SendEmail(address, "Confirm your email", CreateEmailBody(
- $"Please confirm your account.
{CallToAction("Confirm Email", link)}"));
- }
-
- public static void SendApprovalConfirmation(this IEmailSender emailSender, MailboxAddress address, string link)
- {
- emailSender.SendEmail(address, "Your account has been approved", CreateEmailBody(
- $"Your account has been approved and you can now login here."));
- }
-
- public static void SendInvitation(this IEmailSender emailSender, MailboxAddress address, string link)
- {
- emailSender.SendEmail(address, "Invitation", CreateEmailBody(
- $"Please complete your account setup by clicking this link.
You can also use the BTCPay Server app and scan this QR code when connecting:
{GetQrCodeImg(link)}"));
- }
-
- public static void SendNewUserInfo(this IEmailSender emailSender, MailboxAddress address, string newUserInfo, string link)
- {
- emailSender.SendEmail(address, newUserInfo, CreateEmailBody(
- $"{newUserInfo}. You can verify and approve the account here: User details"));
- }
-
public static void SendUserInviteAcceptedInfo(this IEmailSender emailSender, MailboxAddress address, string userInfo, string link)
{
emailSender.SendEmail(address, userInfo, CreateEmailBody(
$"{userInfo}. You can view the store users here: Store users"));
}
-
- private static string GetQrCodeImg(string data)
- {
- using var qrGenerator = new QRCodeGenerator();
- using var qrCodeData = qrGenerator.CreateQrCode(data, QRCodeGenerator.ECCLevel.Q);
- using var qrCode = new Base64QRCode(qrCodeData);
- var base64 = qrCode.GetGraphic(20);
- return $"
";
- }
}
}
diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs
index 1d000a477..0b8bc05ab 100644
--- a/BTCPayServer/Hosting/BTCPayServerServices.cs
+++ b/BTCPayServer/Hosting/BTCPayServerServices.cs
@@ -436,7 +436,6 @@ o.GetRequiredService>().ToDictionary(o => o.P
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
- services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton(s => s.GetRequiredService());
diff --git a/BTCPayServer/Plugins/Emails/EmailsPlugin.cs b/BTCPayServer/Plugins/Emails/EmailsPlugin.cs
index 73537c7ff..1c9ab0402 100644
--- a/BTCPayServer/Plugins/Emails/EmailsPlugin.cs
+++ b/BTCPayServer/Plugins/Emails/EmailsPlugin.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using BTCPayServer.Abstractions.Models;
+using BTCPayServer.Plugins.Emails.HostedServices;
using BTCPayServer.Plugins.Emails.Views;
using BTCPayServer.Plugins.Webhooks;
using BTCPayServer.Services;
@@ -19,6 +20,7 @@ public class EmailsPlugin : BaseBTCPayServerPlugin
{
services.AddSingleton();
services.AddSingleton();
+ services.AddSingleton();
RegisterServerEmailTriggers(services);
}
private static string BODY_STYLE = "font-family: Open Sans, Helvetica Neue,Arial,sans-serif; font-color: #292929;";
@@ -50,8 +52,67 @@ public class EmailsPlugin : BaseBTCPayServerPlugin
Description = "Password Reset Requested",
};
vms.Add(vm);
+
+ vm = new EmailTriggerViewModel()
+ {
+ Trigger = ServerMailTriggers.EmailConfirm,
+ RecipientExample = "{User.MailboxAddress}",
+ SubjectExample = "Confirm your email",
+ BodyExample = CreateEmailBody($"Please confirm your account.
{CallToAction("Confirm Email", "{ConfirmLink}")}"),
+ PlaceHolders = new()
+ {
+ new ("{ConfirmLink}", "The link used to confirm the user's email address")
+ },
+ Description = "Confirm new user's email",
+ };
+ vms.Add(vm);
+
+ vm = new EmailTriggerViewModel()
+ {
+ Trigger = ServerMailTriggers.InvitePending,
+ RecipientExample = "{User.MailboxAddress}",
+ SubjectExample = "Invitation to join {Branding.ServerName}",
+ BodyExample = CreateEmailBody($"Please complete your account setup by clicking this link.
You can also use the BTCPay Server app and scan this QR code when connecting:
{{InvitationLinkQR}}"),
+ PlaceHolders = new()
+ {
+ new ("{InvitationLink}", "The link where the invited user can set up their account"),
+ new ("{InvitationLinkQR}", "The QR code representation of the invitation link")
+ },
+ Description = "User invitation email",
+ };
+ vms.Add(vm);
+
+ vm = new EmailTriggerViewModel()
+ {
+ Trigger = ServerMailTriggers.ApprovalConfirmed,
+ RecipientExample = "{User.MailboxAddress}",
+ SubjectExample = "Your account has been approved",
+ BodyExample = CreateEmailBody($"Your account has been approved and you can now.
{CallToAction("Login here", "{LoginLink}")}"),
+ PlaceHolders = new()
+ {
+ new ("{LoginLink}", "The link that the user can use to login"),
+ },
+ Description = "User account approved",
+ };
+ vms.Add(vm);
+
+ vm = new EmailTriggerViewModel()
+ {
+ Trigger = ServerMailTriggers.ApprovalRequest,
+ RecipientExample = "{Admin.MailboxAddresses}",
+ SubjectExample = "Approval request to access the server for {User.Email}",
+ BodyExample = CreateEmailBody($"A new user ({{User.MailboxAddress}}), is awaiting approval to access the server.
{CallToAction("Approve", "{ApprovalLink}")}"),
+ PlaceHolders = new()
+ {
+ new ("{ApprovalLink}", "The link that the admin needs to use to approve the user"),
+ },
+ Description = "Approval request to administrators",
+ };
+ vms.Add(vm);
+
var commonPlaceholders = new List()
{
+ new("{Admins.MailboxAddresses}", "The email addresses of the admins separated by a comma (eg. ,)"),
new("{User.Name}", "The name of the user (eg. John Doe)"),
new("{User.Email}", "The email of the user (eg. john.doe@example.com)"),
new("{User.MailboxAddress}", "The formatted mailbox address to use when sending an email. (eg. \"John Doe\" )"),
diff --git a/BTCPayServer/Plugins/Emails/EmailRuleProcessorSender.cs b/BTCPayServer/Plugins/Emails/HostedServices/EmailRuleProcessorSender.cs
similarity index 75%
rename from BTCPayServer/Plugins/Emails/EmailRuleProcessorSender.cs
rename to BTCPayServer/Plugins/Emails/HostedServices/EmailRuleProcessorSender.cs
index 68dc707f7..18333964b 100644
--- a/BTCPayServer/Plugins/Emails/EmailRuleProcessorSender.cs
+++ b/BTCPayServer/Plugins/Emails/HostedServices/EmailRuleProcessorSender.cs
@@ -1,6 +1,8 @@
#nullable enable
+using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net.Mail;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Data;
@@ -10,7 +12,7 @@ using Microsoft.Extensions.Logging;
using MimeKit;
using Newtonsoft.Json.Linq;
-namespace BTCPayServer.Plugins.Emails;
+namespace BTCPayServer.Plugins.Emails.HostedServices;
public interface ITriggerOwner
{
@@ -65,13 +67,29 @@ public class StoreEmailRuleProcessorSender(
var subject = new TextTemplate(actionableRule.Subject ?? "");
matchedContext.Recipients.AddRange(
actionableRule.To
- .Select(o =>
+ .SelectMany(o =>
{
- if (!MailboxAddressValidator.TryParse(o, out var oo))
+ if (MailboxAddressValidator.TryParse(o, out var oo))
+ return new[] { oo };
+
+ var emails = new TextTemplate(o).Render(triggEvent.Model);
+ MailAddressCollection mailCollection = new();
+ try
{
- MailboxAddressValidator.TryParse(new TextTemplate(o).Render(triggEvent.Model), out oo);
+ mailCollection.Add(emails);
}
- return oo;
+ catch (FormatException)
+ {
+ return Array.Empty();
+ }
+
+ return mailCollection.Select(a =>
+ {
+ MailboxAddressValidator.TryParse(a.ToString(), out oo);
+ return oo;
+ })
+ .Where(a => a != null)
+ .ToArray();
})
.Where(o => o != null)!);
diff --git a/BTCPayServer/HostedServices/UserEventHostedService.cs b/BTCPayServer/Plugins/Emails/HostedServices/UserEventTriggerHostedService.cs
similarity index 61%
rename from BTCPayServer/HostedServices/UserEventHostedService.cs
rename to BTCPayServer/Plugins/Emails/HostedServices/UserEventTriggerHostedService.cs
index 178663e87..eb9a9e9f4 100644
--- a/BTCPayServer/HostedServices/UserEventHostedService.cs
+++ b/BTCPayServer/Plugins/Emails/HostedServices/UserEventTriggerHostedService.cs
@@ -1,10 +1,12 @@
+#nullable enable
+using System.Linq;
using System.Text.Encodings.Web;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Data;
using BTCPayServer.Events;
+using BTCPayServer.HostedServices;
using BTCPayServer.Logging;
-using BTCPayServer.Plugins.Emails;
using BTCPayServer.Services;
using BTCPayServer.Services.Mails;
using BTCPayServer.Services.Notifications;
@@ -13,13 +15,13 @@ using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
+using QRCoder;
-namespace BTCPayServer.HostedServices;
+namespace BTCPayServer.Plugins.Emails.HostedServices;
public class UserEventHostedService(
EventAggregator eventAggregator,
UserManager userManager,
- CallbackGenerator callbackGenerator,
ISettingsAccessor serverSettings,
EmailSenderFactory emailSenderFactory,
NotificationSender notificationSender,
@@ -28,62 +30,64 @@ public class UserEventHostedService(
: EventHostedServiceBase(eventAggregator, logs)
{
public UserManager UserManager { get; } = userManager;
- public CallbackGenerator CallbackGenerator { get; } = callbackGenerator;
protected override void SubscribeToEvents()
{
- Subscribe();
- Subscribe();
- Subscribe();
- Subscribe();
- Subscribe();
- Subscribe();
+ SubscribeAny();
+ }
+
+ public static string GetQrCodeImg(string data)
+ {
+ using var qrGenerator = new QRCodeGenerator();
+ using var qrCodeData = qrGenerator.CreateQrCode(data, QRCodeGenerator.ECCLevel.Q);
+ using var qrCode = new Base64QRCode(qrCodeData);
+ var base64 = qrCode.GetGraphic(20);
+ return $"
";
}
protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken)
{
- ApplicationUser user = (evt as UserEvent).User;
- IEmailSender emailSender;
+ var user = (evt as UserEvent)?.User;
+ if (user is null) return;
switch (evt)
{
case UserEvent.Registered ev:
- // can be either a self-registration or by invite from another user
- var type = await UserManager.IsInRoleAsync(user, Roles.ServerAdmin) ? "admin" : "user";
- var info = ev switch
- {
- UserEvent.Invited { InvitedByUser: { } invitedBy } => $"invited by {invitedBy.Email}",
- UserEvent.Invited => "invited",
- _ => "registered"
- };
- var requiresApproval = user.RequiresApproval && !user.Approved;
- var requiresEmailConfirmation = user.RequiresEmailConfirmation && !user.EmailConfirmed;
-
- // log registration info
- var newUserInfo = $"New {type} {user.Email} {info}";
- Logs.PayServer.LogInformation(newUserInfo);
+ var requiresApproval = user is { RequiresApproval: true, Approved: false };
+ var requiresEmailConfirmation = user is { RequiresEmailConfirmation: true, EmailConfirmed: false };
// send notification if the user does not require email confirmation.
// inform admins only about qualified users and not annoy them with bot registrations.
if (requiresApproval && !requiresEmailConfirmation)
{
- await NotifyAdminsAboutUserRequiringApproval(user, ev.ApprovalLink, newUserInfo);
+ await NotifyAdminsAboutUserRequiringApproval(user, ev.ApprovalLink);
}
// set callback result and send email to user
- emailSender = await emailSenderFactory.GetEmailSender();
if (ev is UserEvent.Invited invited)
{
if (invited.SendInvitationEmail)
- emailSender.SendInvitation(user.GetMailboxAddress(), invited.InvitationLink);
+ EventAggregator.Publish(await CreateTriggerEvent(ServerMailTriggers.InvitePending,
+ new JObject()
+ {
+ ["InvitationLink"] = HtmlEncoder.Default.Encode(invited.InvitationLink),
+ ["InvitationLinkQR"] = GetQrCodeImg(invited.InvitationLink)
+ }, user));
}
else if (requiresEmailConfirmation)
{
- emailSender.SendEmailConfirmation(user.GetMailboxAddress(), ev.ConfirmationEmailLink);
+ EventAggregator.Publish(new UserEvent.ConfirmationEmailRequested(user, ev.ConfirmationEmailLink));
}
break;
+ case UserEvent.ConfirmationEmailRequested confReq:
+ EventAggregator.Publish(await CreateTriggerEvent(ServerMailTriggers.EmailConfirm,
+ new JObject()
+ {
+ ["ConfirmLink"] = HtmlEncoder.Default.Encode(confReq.ConfirmLink)
+ }, user));
+ break;
case UserEvent.PasswordResetRequested pwResetEvent:
- EventAggregator.Publish(CreateTriggerEvent(ServerMailTriggers.PasswordReset,
+ EventAggregator.Publish(await CreateTriggerEvent(ServerMailTriggers.PasswordReset,
new JObject()
{
["ResetLink"] = HtmlEncoder.Default.Encode(pwResetEvent.ResetLink)
@@ -92,15 +96,16 @@ public class UserEventHostedService(
case UserEvent.Approved approvedEvent:
if (!user.Approved) break;
- emailSender = await emailSenderFactory.GetEmailSender();
- emailSender.SendApprovalConfirmation(user.GetMailboxAddress(), approvedEvent.LoginLink);
+ EventAggregator.Publish(await CreateTriggerEvent(ServerMailTriggers.ApprovalConfirmed,
+ new JObject()
+ {
+ ["LoginLink"] = approvedEvent.LoginLink
+ }, user));
+
break;
- case UserEvent.ConfirmedEmail confirmedEvent:
- if (!user.EmailConfirmed) break;
- var confirmedUserInfo = $"User {user.Email} confirmed their email address";
- Logs.PayServer.LogInformation(confirmedUserInfo);
- await NotifyAdminsAboutUserRequiringApproval(user, confirmedEvent.ApprovalLink, confirmedUserInfo);
+ case UserEvent.ConfirmedEmail confirmedEvent when user is { RequiresApproval: true, Approved: false, EmailConfirmed: true }:
+ await NotifyAdminsAboutUserRequiringApproval(user, confirmedEvent.ApprovalLink);
break;
case UserEvent.InviteAccepted inviteAcceptedEvent:
@@ -110,8 +115,24 @@ public class UserEventHostedService(
}
}
- private TriggerEvent CreateTriggerEvent(string trigger, JObject model, ApplicationUser user)
+ private async Task NotifyAdminsAboutUserRequiringApproval(ApplicationUser user, string approvalLink)
{
+ await notificationSender.SendNotification(new AdminScope(), new NewUserRequiresApprovalNotification(user));
+ EventAggregator.Publish(await CreateTriggerEvent(ServerMailTriggers.ApprovalRequest,
+ new JObject()
+ {
+ ["ApprovalLink"] = approvalLink
+ }, user));
+ }
+
+ private async Task CreateTriggerEvent(string trigger, JObject model, ApplicationUser user)
+ {
+ var admins = await UserManager.GetUsersInRoleAsync(Roles.ServerAdmin);
+ var adminMailboxes = string.Join(", ", admins.Select(a => a.GetMailboxAddress().ToString()).ToArray());
+ model["Admins"] = new JObject()
+ {
+ ["MailboxAddresses"] = adminMailboxes,
+ };
model["User"] = new JObject()
{
["Name"] = user.UserName,
@@ -126,21 +147,6 @@ public class UserEventHostedService(
var evt = new TriggerEvent(null, trigger, model, null);
return evt;
}
-
- private async Task NotifyAdminsAboutUserRequiringApproval(ApplicationUser user, string approvalLink, string newUserInfo)
- {
- if (!user.RequiresApproval || user.Approved) return;
- // notification
- await notificationSender.SendNotification(new AdminScope(), new NewUserRequiresApprovalNotification(user));
- // email
- var admins = await UserManager.GetUsersInRoleAsync(Roles.ServerAdmin);
- var emailSender = await emailSenderFactory.GetEmailSender();
- foreach (var admin in admins)
- {
- emailSender.SendNewUserInfo(admin.GetMailboxAddress(), newUserInfo, approvalLink);
- }
- }
-
private async Task NotifyAboutUserAcceptingInvite(ApplicationUser user, string storeUsersLink)
{
var stores = await storeRepository.GetStoresByUserId(user.Id);
diff --git a/BTCPayServer/Plugins/Emails/ServerMailTriggers.cs b/BTCPayServer/Plugins/Emails/ServerMailTriggers.cs
index c5ee029b3..50a16231f 100644
--- a/BTCPayServer/Plugins/Emails/ServerMailTriggers.cs
+++ b/BTCPayServer/Plugins/Emails/ServerMailTriggers.cs
@@ -8,4 +8,5 @@ public class ServerMailTriggers
public const string ApprovalConfirmed = "SRV-ApprovalConfirmed";
public const string ApprovalPending = "SRV-ApprovalPending";
public const string EmailConfirm = "SRV-EmailConfirmation";
+ public const string ApprovalRequest = "SRV-ApprovalRequest";
}
diff --git a/BTCPayServer/Plugins/Emails/Views/EmailTriggerViewModel.cs b/BTCPayServer/Plugins/Emails/Views/EmailTriggerViewModel.cs
index 66a81e672..933cce160 100644
--- a/BTCPayServer/Plugins/Emails/Views/EmailTriggerViewModel.cs
+++ b/BTCPayServer/Plugins/Emails/Views/EmailTriggerViewModel.cs
@@ -7,11 +7,18 @@ namespace BTCPayServer.Plugins.Emails.Views;
///
public class EmailTriggerViewModel
{
+ public class Default
+ {
+ public string SubjectExample { get; set; }
+ public string BodyExample { get; set; }
+ public bool CanIncludeCustomerEmail { get; set; }
+ public string RecipientExample { get; set; }
+ }
+
public string Trigger { get; set; }
+
public string Description { get; set; }
- public string SubjectExample { get; set; }
- public string BodyExample { get; set; }
- public bool CanIncludeCustomerEmail { get; set; }
+
public class PlaceHolder(string name, string description)
{
@@ -21,5 +28,4 @@ public class EmailTriggerViewModel
public List PlaceHolders { get; set; } = new();
public bool ServerTrigger { get; set; }
- public string RecipientExample { get; set; }
}
diff --git a/BTCPayServer/Plugins/Emails/Views/Shared/EmailRulesManage.cshtml b/BTCPayServer/Plugins/Emails/Views/Shared/EmailRulesManage.cshtml
index 605c751be..152a2c9c3 100644
--- a/BTCPayServer/Plugins/Emails/Views/Shared/EmailRulesManage.cshtml
+++ b/BTCPayServer/Plugins/Emails/Views/Shared/EmailRulesManage.cshtml
@@ -152,31 +152,19 @@
const bodyTextarea = document.querySelector('.email-rule-body');
const placeholdersTd = document.querySelector('#placeholders');
- const isEmptyOrDefault = (value, type) => {
- const val = value.replace(/<.*?>/gi, '').trim();
- if (!val) return true;
- return Object.values(triggersByType).some(t => t[type] === val);
- };
-
function applyTemplate() {
const selectedTrigger = triggerSelect.value;
- console.log(selectedTrigger);
if (triggersByType[selectedTrigger]) {
- if (isEmptyOrDefault(subjectInput.value, 'subjectExample')) {
- subjectInput.value = triggersByType[selectedTrigger].subjectExample;
- }
- if (isEmptyOrDefault(recipientInput.value, 'recipientExample')) {
- recipientInput.value = triggersByType[selectedTrigger].recipientExample;
- }
+ subjectInput.value = triggersByType[selectedTrigger].subjectExample;
+ recipientInput.value = triggersByType[selectedTrigger].recipientExample;
var body = triggersByType[selectedTrigger].bodyExample;
- if (isEmptyOrDefault(bodyTextarea.value, 'bodyExample')) {
- if ($(bodyTextarea).summernote) {
- $(bodyTextarea).summernote('reset');
- $(bodyTextarea).summernote('code', body.replace(/\n/g, '
'));
- } else {
- bodyTextarea.value = body;
- }
+ if ($(bodyTextarea).summernote) {
+ console.log(body);
+ $(bodyTextarea).summernote('reset');
+ $(bodyTextarea).summernote('code', body.replace(/\n/g, '
'));
+ } else {
+ bodyTextarea.value = body;
}
placeholdersTd.innerHTML = '';
@@ -213,7 +201,10 @@
// Apply template on page load if a trigger is selected
if (triggerSelect.value) {
- applyTemplate();
+ if (@Safe.Json(!isEdit))
+ {
+ applyTemplate();
+ }
toggleCustomerEmailVisibility();
}
});
diff --git a/BTCPayServer/Plugins/Webhooks/HostedServices/WebhookProviderHostedService.cs b/BTCPayServer/Plugins/Webhooks/HostedServices/WebhookProviderHostedService.cs
index af6f15df1..6f94b4ba2 100644
--- a/BTCPayServer/Plugins/Webhooks/HostedServices/WebhookProviderHostedService.cs
+++ b/BTCPayServer/Plugins/Webhooks/HostedServices/WebhookProviderHostedService.cs
@@ -7,6 +7,7 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.HostedServices;
using BTCPayServer.Plugins.Emails;
+using BTCPayServer.Plugins.Emails.HostedServices;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using StoreData = BTCPayServer.Data.StoreData;
diff --git a/BTCPayServer/Plugins/Webhooks/TriggerProviders/InvoiceTriggerProvider.cs b/BTCPayServer/Plugins/Webhooks/TriggerProviders/InvoiceTriggerProvider.cs
index 1bd661dc4..49eee8c4a 100644
--- a/BTCPayServer/Plugins/Webhooks/TriggerProviders/InvoiceTriggerProvider.cs
+++ b/BTCPayServer/Plugins/Webhooks/TriggerProviders/InvoiceTriggerProvider.cs
@@ -4,6 +4,7 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Controllers.Greenfield;
using BTCPayServer.Events;
using BTCPayServer.Plugins.Emails;
+using BTCPayServer.Plugins.Emails.HostedServices;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Newtonsoft.Json.Linq;
diff --git a/BTCPayServer/Plugins/Webhooks/TriggerProviders/PaymentRequestTriggerProvider.cs b/BTCPayServer/Plugins/Webhooks/TriggerProviders/PaymentRequestTriggerProvider.cs
index bf6431c04..39aeb3ab2 100644
--- a/BTCPayServer/Plugins/Webhooks/TriggerProviders/PaymentRequestTriggerProvider.cs
+++ b/BTCPayServer/Plugins/Webhooks/TriggerProviders/PaymentRequestTriggerProvider.cs
@@ -6,6 +6,7 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.HostedServices;
using BTCPayServer.Plugins.Emails;
+using BTCPayServer.Plugins.Emails.HostedServices;
using BTCPayServer.Services.PaymentRequests;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
diff --git a/BTCPayServer/Plugins/Webhooks/WebhookTriggerProvider.cs b/BTCPayServer/Plugins/Webhooks/WebhookTriggerProvider.cs
index db132c128..559f290d4 100644
--- a/BTCPayServer/Plugins/Webhooks/WebhookTriggerProvider.cs
+++ b/BTCPayServer/Plugins/Webhooks/WebhookTriggerProvider.cs
@@ -3,6 +3,7 @@ using System.Threading.Tasks;
using BTCPayServer.Client.Models;
using BTCPayServer.HostedServices;
using BTCPayServer.Plugins.Emails;
+using BTCPayServer.Plugins.Emails.HostedServices;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using StoreData = BTCPayServer.Data.StoreData;
diff --git a/BTCPayServer/Services/Notifications/NotificationSender.cs b/BTCPayServer/Services/Notifications/NotificationSender.cs
index e919d0242..31a7a8861 100644
--- a/BTCPayServer/Services/Notifications/NotificationSender.cs
+++ b/BTCPayServer/Services/Notifications/NotificationSender.cs
@@ -48,10 +48,7 @@ namespace BTCPayServer.Services.Notifications
}
await db.SaveChangesAsync();
}
- foreach (string user in users)
- {
- _notificationManager.InvalidateNotificationCache(user);
- }
+ _notificationManager.InvalidateNotificationCache(users);
}
public BaseNotification GetBaseNotification(NotificationData notificationData)
diff --git a/BTCPayServer/Validation/MailboxAddressValidator.cs b/BTCPayServer/Validation/MailboxAddressValidator.cs
index 4424be4d6..c7077bf25 100644
--- a/BTCPayServer/Validation/MailboxAddressValidator.cs
+++ b/BTCPayServer/Validation/MailboxAddressValidator.cs
@@ -8,7 +8,7 @@ using MimeKit;
namespace BTCPayServer
{
///
- /// Validate address in the format "Firstname Lastname " See rfc822
+ /// Validate address in the format "Firstname Lastname " See rfc5322
///
public class MailboxAddressValidator
{
@@ -25,7 +25,7 @@ namespace BTCPayServer
public static MailboxAddress Parse(string? str)
{
if (!TryParse(str, out var mb))
- throw new FormatException("Invalid mailbox address (rfc822)");
+ throw new FormatException("Invalid mailbox address (rfc5322)");
return mb;
}
public static bool TryParse(string? str, [MaybeNullWhen(false)] out MailboxAddress mailboxAddress)