Implemented plain text content in newsletter emails

This commit is contained in:
Mia Rose Winter 2024-02-22 17:32:06 +01:00
parent 22c6018e14
commit c8b7f9f44f
Signed by: miawinter
GPG key ID: 4B6F6A83178F595E
7 changed files with 56 additions and 25 deletions

View file

@ -1,4 +1,5 @@
using System.Collections.Frozen;
using System.Globalization;
using System.Net;
using System.Text;
using Microsoft.Extensions.Options;
@ -12,31 +13,36 @@ public class EmailFactory(IOptions<Customization> customizations, EmailTemplateS
private Customization Customizations { get; } = customizations.Value;
private EmailTemplateService TemplateService { get; } = templateService;
public async ValueTask<IEmail> CreateDefaultEmail(string receiverMail, string? receiverName, string subject, string title, string bodyHtml) {
public async ValueTask<IEmail> CreateDefaultEmail(string receiverMail, string? receiverName, string subject, string title, string bodyHtml, string bodyPlain = "") {
(string host, string logo) = GetStaticData();
string body = await TemplateService.DefaultAsync(host, logo, title, bodyHtml);
return new StaticEmail(receiverMail, receiverName, subject, title, body, FrozenDictionary<string, string>.Empty);
return new StaticEmail(receiverMail, receiverName, subject, title, body, $"{title}\n\n{bodyPlain}", FrozenDictionary<string, string>.Empty);
}
public async ValueTask<IEmail> CreateSubscribedEmail(EmailSubscriber subscriber, string browserLink, string subject, string title, string bodyHtml, string role = "unknown", string? replyTo = null) {
public async ValueTask<IEmail> CreateSubscribedEmail(EmailSubscriber subscriber, string browserLink, string subject, string title, string bodyHtml, string bodyPlain = "", string role = "unknown", string? replyTo = null) {
(string host, string logo) = GetStaticData();
string unsubscribeLink = await GetUnsubscribeLink(host, subscriber.Id, role);
string body = await TemplateService.NewsletterAsync(host, browserLink, logo, title, bodyHtml, unsubscribeLink);
string footer = await TemplateService.GetPartialAsync("email-plain-footer");
bodyPlain += "\n\n" + footer.Replace(
$"[[{EmailTemplateService.Constants.EmailUnsubscribeLink}]]",
unsubscribeLink, true, CultureInfo.InvariantCulture);
var headers = new Dictionary<string, string>{
{HeaderId.ListUnsubscribe.ToHeaderName(), $"<{unsubscribeLink}>"},
{HeaderId.ListUnsubscribePost.ToHeaderName(), "One-Click"}
};
if (!string.IsNullOrWhiteSpace(replyTo)) headers.Add(HeaderId.ReplyTo.ToHeaderName(), replyTo);
return new StaticEmail(subscriber.Email, subscriber.Name, subject, title, body, headers.ToFrozenDictionary());
return new StaticEmail(subscriber.Email, subscriber.Name, subject, title, body, $"{title}\n\n{bodyPlain}", headers.ToFrozenDictionary());
}
public async ValueTask<IEmail> CreateWelcomeEmail(EmailSubscriber subscriber, IEnumerable<EmailNewsletter> articles, string subject, string title, string bodyHtml) {
public async ValueTask<IEmail> CreateWelcomeEmail(EmailSubscriber subscriber, IEnumerable<EmailNewsletter> articles, string subject, string title, string bodyHtml, string bodyPlain = "") {
(string host, string logo) = GetStaticData();
string articlePartial = await TemplateService.GetPartialAsync("email-article");
string footer = await TemplateService.GetPartialAsync("email-plain-footer");
var articlesHtml = new StringBuilder("");
foreach (var n in articles) {
string articleLink = ArticleUtilities.GenerateArticleLink(n.Article, new Uri(Customizations.AppUrl, UriKind.Absolute));
@ -47,8 +53,12 @@ public class EmailFactory(IOptions<Customization> customizations, EmailTemplateS
string unsubscribeLink = await GetUnsubscribeLink(host, subscriber.Id, "welcome");
string body = TemplateService.Welcome(host, logo, title, bodyHtml, unsubscribeLink, articlesHtml.ToString());
bodyPlain += "\n" + HtmlUtilities.GetPlainText(articlesHtml.ToString());
bodyPlain += "\n\n" + footer.Replace(
$"[[{EmailTemplateService.Constants.EmailUnsubscribeLink}]]",
unsubscribeLink, true, CultureInfo.InvariantCulture);
return new StaticEmail(subscriber.Email, subscriber.Name, subject, title, body, new Dictionary<string, string>{
return new StaticEmail(subscriber.Email, subscriber.Name, subject, title, body, $"{title}\n\n{bodyPlain}", new Dictionary<string, string>{
{HeaderId.ListUnsubscribe.ToHeaderName(), $"<{unsubscribeLink}>"},
{HeaderId.ListUnsubscribePost.ToHeaderName(), "One-Click"}
}.ToFrozenDictionary());

View file

@ -97,6 +97,7 @@ public enum Constants {
FileSystem.GetEmailTemplate("newsletter", DefaultTemplates["newsletter"]);
FileSystem.GetEmailTemplate("welcome", DefaultTemplates["welcome"]);
FileSystem.GetPartialTemplate("email-article", DefaultPartials["email-article"]);
FileSystem.GetPartialTemplate("email-plain-footer", DefaultPartials["email-plain-footer"]);
}
public string ApplyTokens(string template, Func<string, string?> replacer) {
@ -132,7 +133,7 @@ public enum Constants {
return html;
}
public string Process(string templateName, Dictionary<Constants, object?> data) {
string template = ApplyTokens(GetTemplate(templateName), token =>
data.TryGetValue(Enum.Parse<Constants>(token, true), out object? v) ? v?.ToString() : null);
@ -307,6 +308,10 @@ public enum Constants {
<a href="{3}">Link</a>
</div>
"""
},
{
"email-plain-footer",
$"Unsubscribe: [[{Constants.EmailUnsubscribeLink}]]"
}
};
}

View file

@ -8,5 +8,6 @@ public interface IEmail {
string Subject { get; }
string Title { get; }
string ContentHtml { get; }
string ContentPlain { get; }
FrozenDictionary<string, string> Headers { get; }
}

View file

@ -52,7 +52,10 @@ public class LiveEmailService(ILogger<LiveEmailService> logger, IOptions<EmailCo
To = { new MailboxAddress(email.ReceiverName, email.ReceiverEmail) },
Subject = email.Subject
};
var builder = new BodyBuilder { HtmlBody = email.ContentHtml };
var builder = new BodyBuilder {
HtmlBody = email.ContentHtml,
TextBody = email.ContentPlain
};
message.Body = builder.ToMessageBody();
foreach ((string id, string value) in email.Headers) {
if (id == HeaderId.ListUnsubscribe.ToHeaderName()) {

View file

@ -55,8 +55,12 @@ public class NewsletterBackgroundService(ILogger<NewsletterBackgroundService> lo
last = subscribers.Last();
foreach (var subscriber in subscribers) {
var email = await factory.CreateSubscribedEmail(subscriber, articleLink, newsletter.Article.Title,
newsletter.Article.Title, newsletter.Article.BodyHtml, newsletter.Id.ToString(), replyTo);
var email = await factory.CreateSubscribedEmail(subscriber, articleLink,
newsletter.Article.Title,
newsletter.Article.Title,
newsletter.Article.BodyHtml,
newsletter.Article.BodyPlain,
"newsletter-" + newsletter.Id, replyTo);
await client.SendEmailAsync(email);
}
}

View file

@ -1,5 +1,7 @@
using Microsoft.AspNetCore.Identity;
using Azure.Core;
using Microsoft.AspNetCore.Identity;
using Wave.Data;
using Wave.Utilities;
namespace Wave.Services;
@ -11,16 +13,19 @@ public class SmtpEmailSender(EmailFactory email, [FromKeyedServices("live")]IEma
#region IEmailSenderAsync<ApplicationUser>
public Task SendConfirmationLinkAsync(ApplicationUser user, string email, string confirmationLink) =>
SendEmailAsync(email, user.FullName, "Confirm your email",
$"Please confirm your account by <a href='{confirmationLink}'>clicking here</a>.");
SendDefaultMailAsync(email, user.FullName, "Confirm your email", "Confirm your email",
$"<p>Please confirm your account by <a href='{confirmationLink}'>clicking here</a>.</p>",
$"Please confirm your account by clicking here: {confirmationLink}");
public Task SendPasswordResetLinkAsync(ApplicationUser user, string email, string resetLink) =>
SendEmailAsync(email, user.FullName, "Reset your password",
$"Please reset your password by <a href='{resetLink}'>clicking here</a>.");
SendDefaultMailAsync(email, user.FullName, "Reset your password", "Reset your password",
$"<p>Please reset your password by <a href='{resetLink}'>clicking here</a>.</p>",
$"Please reset your password by clicking here: {resetLink}");
public Task SendPasswordResetCodeAsync(ApplicationUser user, string email, string resetCode) =>
SendEmailAsync(email, user.FullName, "Reset your password",
$"Please reset your password using the following code: {resetCode}");
SendDefaultMailAsync(email, user.FullName, "Reset your password", "Reset your password",
$"<p>Please reset your password using the following code: {resetCode}.</p>",
$"Please reset your password using the following code: {resetCode}.");
#endregion
@ -35,27 +40,33 @@ public class SmtpEmailSender(EmailFactory email, [FromKeyedServices("live")]IEma
public async Task SendEmailAsync(string email, string? name, string subject, string htmlMessage) {
await EmailService.ConnectAsync(CancellationToken.None);
await EmailService.SendEmailAsync(await Email.CreateDefaultEmail(email, name, subject, subject, htmlMessage));
await EmailService.SendEmailAsync(await Email.CreateDefaultEmail(email, name, subject, subject, htmlMessage, HtmlUtilities.GetPlainText(htmlMessage)));
await EmailService.DisconnectAsync(CancellationToken.None);
}
public async Task SendDefaultMailAsync(string receiverMail, string? receiverName, string subject, string title, string bodyHtml) {
await EmailService.ConnectAsync(CancellationToken.None);
var email = await Email.CreateDefaultEmail(receiverMail, receiverName, subject, title, bodyHtml);
var email = await Email.CreateDefaultEmail(receiverMail, receiverName, subject, title, bodyHtml, HtmlUtilities.GetPlainText(bodyHtml));
await EmailService.SendEmailAsync(email);
await EmailService.DisconnectAsync(CancellationToken.None);
}
public async Task SendDefaultMailAsync(string receiverMail, string? receiverName, string subject, string title, string bodyHtml, string bodyPlain) {
await EmailService.ConnectAsync(CancellationToken.None);
var email = await Email.CreateDefaultEmail(receiverMail, receiverName, subject, title, bodyHtml, bodyPlain);
await EmailService.SendEmailAsync(email);
await EmailService.DisconnectAsync(CancellationToken.None);
}
public async Task SendSubscribedMailAsync(EmailSubscriber subscriber, string subject, string title, string bodyHtml,
string browserUrl = "", string subscribedRole = "-1") {
var email = await Email.CreateSubscribedEmail(subscriber, browserUrl, subject, title, bodyHtml, subscribedRole);
var email = await Email.CreateSubscribedEmail(subscriber, browserUrl, subject, title, bodyHtml, HtmlUtilities.GetPlainText(bodyHtml), subscribedRole);
await BulkEmailService.ConnectAsync(CancellationToken.None);
await BulkEmailService.SendEmailAsync(email);
}
public async Task SendWelcomeMailAsync(EmailSubscriber subscriber, string subject, string title, string bodyHtml,
IEnumerable<EmailNewsletter> articles) {
var email = await Email.CreateWelcomeEmail(subscriber, articles, subject, title, bodyHtml);
var email = await Email.CreateWelcomeEmail(subscriber, articles, subject, title, bodyHtml, HtmlUtilities.GetPlainText(bodyHtml));
await BulkEmailService.ConnectAsync(CancellationToken.None);
await BulkEmailService.SendEmailAsync(email);
}

View file

@ -1,8 +1,5 @@
using System.Collections.Frozen;
using Microsoft.Extensions.Hosting;
using Mjml.Net.Helpers;
namespace Wave.Services;
public record StaticEmail(string ReceiverEmail, string? ReceiverName, string Subject, string Title, string ContentHtml,
FrozenDictionary<string, string> Headers) : IEmail;
public record StaticEmail(string ReceiverEmail, string? ReceiverName, string Subject, string Title, string ContentHtml, string ContentPlain, FrozenDictionary<string, string> Headers) : IEmail;