diff --git a/src/BuildingBlocks/Mailing/HtmlEmail.cs b/src/BuildingBlocks/Mailing/HtmlEmail.cs
new file mode 100644
index 0000000000..a4fc4ae565
--- /dev/null
+++ b/src/BuildingBlocks/Mailing/HtmlEmail.cs
@@ -0,0 +1,107 @@
+using System.Net;
+
+namespace FSH.Framework.Mailing;
+
+///
+
|
+
{safeIntro}
++ {safeLabel} +
+
+ If the button does not work, copy this address into your browser:
+ {safeUrl}
+
{Encode(message)}
"""); + } +} diff --git a/src/BuildingBlocks/Mailing/MailRequest.cs b/src/BuildingBlocks/Mailing/MailRequest.cs index 597d2e9728..fb20f3a6c5 100644 --- a/src/BuildingBlocks/Mailing/MailRequest.cs +++ b/src/BuildingBlocks/Mailing/MailRequest.cs @@ -2,14 +2,26 @@ namespace FSH.Framework.Mailing; -public class MailRequest(CollectionHi {Escape(tenantName)},
" + - $"Your {Escape(planKey ?? "current")} subscription is valid until " + + $"
Hi {HtmlEmail.Encode(tenantName)},
" + + $"Your {HtmlEmail.Encode(plan)} subscription is valid until " + $"{Date(validUpto)} ({daysRemaining} day(s) remaining).
" + "Please contact your account operator to renew and avoid any interruption to your service.
"); - return (subject, body); + var text = Text(subject, + $"Hi {tenantName},", + $"Your {plan} subscription is valid until {Date(validUpto)} ({daysRemaining} day(s) remaining).", + "Please contact your account operator to renew and avoid any interruption to your service."); + return (subject, body, text); } - public static (string Subject, string Body) EnteredGrace(string tenantName, string? planKey, DateTime validUpto, DateTime graceEnds) + public static (string Subject, string Body, string TextBody) EnteredGrace(string tenantName, string? planKey, DateTime validUpto, DateTime graceEnds) { const string subject = "Your subscription has lapsed — grace period active"; + var plan = planKey ?? "current"; var body = Wrap(subject, - $"Hi {Escape(tenantName)},
" + - $"Your {Escape(planKey ?? "current")} subscription expired on " + + $"
Hi {HtmlEmail.Encode(tenantName)},
" + + $"Your {HtmlEmail.Encode(plan)} subscription expired on " + $"{Date(validUpto)}. Your service continues during a grace period that ends on " + $"{Date(graceEnds)}.
" + "Please renew before the grace period ends to keep your access uninterrupted.
"); - return (subject, body); + var text = Text(subject, + $"Hi {tenantName},", + $"Your {plan} subscription expired on {Date(validUpto)}. Your service continues during a grace period that ends on {Date(graceEnds)}.", + "Please renew before the grace period ends to keep your access uninterrupted."); + return (subject, body, text); } - public static (string Subject, string Body) Expired(string tenantName, string? planKey, DateTime validUpto) + public static (string Subject, string Body, string TextBody) Expired(string tenantName, string? planKey, DateTime validUpto) { const string subject = "Your subscription has expired"; + var plan = planKey ?? "current"; var body = Wrap(subject, - $"Hi {Escape(tenantName)},
" + - $"Your {Escape(planKey ?? "current")} subscription expired on " + + $"
Hi {HtmlEmail.Encode(tenantName)},
" + + $"Your {HtmlEmail.Encode(plan)} subscription expired on " + $"{Date(validUpto)} and the grace period has ended, so access is now suspended.
" + "Contact your account operator to renew and restore access.
"); - return (subject, body); + var text = Text(subject, + $"Hi {tenantName},", + $"Your {plan} subscription expired on {Date(validUpto)} and the grace period has ended, so access is now suspended.", + "Contact your account operator to renew and restore access."); + return (subject, body, text); } - public static (string Subject, string Body) InvoiceIssued(string invoiceNumber, decimal amount, string currency, DateTime? dueAtUtc) + public static (string Subject, string Body, string TextBody) InvoiceIssued(string invoiceNumber, decimal amount, string currency, DateTime? dueAtUtc) { var subject = $"Invoice {invoiceNumber} issued"; var amountText = $"{amount.ToString("0.00", CultureInfo.InvariantCulture)} {currency}"; var due = dueAtUtc is null ? string.Empty : $"Due by {Date(dueAtUtc.Value)}.
"; var body = Wrap(subject, - $"A new invoice {Escape(invoiceNumber)} for {amountText} has been issued.
" + + // amountText embeds the currency, which is data rather than a literal, so it is encoded + // for the HTML part while the text/plain twin below keeps it verbatim. + $"A new invoice {HtmlEmail.Encode(invoiceNumber)} for {HtmlEmail.Encode(amountText)} has been issued.
" + due + "You can view and download this invoice from your dashboard.
"); - return (subject, body); + var text = Text(subject, + $"A new invoice {invoiceNumber} for {amountText} has been issued.", + dueAtUtc is null ? string.Empty : $"Due by {Date(dueAtUtc.Value)}.", + "You can view and download this invoice from your dashboard."); + return (subject, body, text); } + ///This is an automated message.
" + - "This is an automated message.
" + + "