Implemented plain text content in newsletter emails
This commit is contained in:
parent
22c6018e14
commit
c8b7f9f44f
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Frozen;
|
using System.Collections.Frozen;
|
||||||
|
using System.Globalization;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
@ -12,31 +13,36 @@ public class EmailFactory(IOptions<Customization> customizations, EmailTemplateS
|
||||||
private Customization Customizations { get; } = customizations.Value;
|
private Customization Customizations { get; } = customizations.Value;
|
||||||
private EmailTemplateService TemplateService { get; } = templateService;
|
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 host, string logo) = GetStaticData();
|
||||||
string body = await TemplateService.DefaultAsync(host, logo, title, bodyHtml);
|
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 host, string logo) = GetStaticData();
|
||||||
|
|
||||||
string unsubscribeLink = await GetUnsubscribeLink(host, subscriber.Id, role);
|
string unsubscribeLink = await GetUnsubscribeLink(host, subscriber.Id, role);
|
||||||
string body = await TemplateService.NewsletterAsync(host, browserLink, logo, title, bodyHtml, unsubscribeLink);
|
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>{
|
var headers = new Dictionary<string, string>{
|
||||||
{HeaderId.ListUnsubscribe.ToHeaderName(), $"<{unsubscribeLink}>"},
|
{HeaderId.ListUnsubscribe.ToHeaderName(), $"<{unsubscribeLink}>"},
|
||||||
{HeaderId.ListUnsubscribePost.ToHeaderName(), "One-Click"}
|
{HeaderId.ListUnsubscribePost.ToHeaderName(), "One-Click"}
|
||||||
};
|
};
|
||||||
if (!string.IsNullOrWhiteSpace(replyTo)) headers.Add(HeaderId.ReplyTo.ToHeaderName(), replyTo);
|
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 host, string logo) = GetStaticData();
|
||||||
|
|
||||||
string articlePartial = await TemplateService.GetPartialAsync("email-article");
|
string articlePartial = await TemplateService.GetPartialAsync("email-article");
|
||||||
|
string footer = await TemplateService.GetPartialAsync("email-plain-footer");
|
||||||
var articlesHtml = new StringBuilder("");
|
var articlesHtml = new StringBuilder("");
|
||||||
foreach (var n in articles) {
|
foreach (var n in articles) {
|
||||||
string articleLink = ArticleUtilities.GenerateArticleLink(n.Article, new Uri(Customizations.AppUrl, UriKind.Absolute));
|
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 unsubscribeLink = await GetUnsubscribeLink(host, subscriber.Id, "welcome");
|
||||||
string body = TemplateService.Welcome(host, logo, title, bodyHtml, unsubscribeLink, articlesHtml.ToString());
|
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.ListUnsubscribe.ToHeaderName(), $"<{unsubscribeLink}>"},
|
||||||
{HeaderId.ListUnsubscribePost.ToHeaderName(), "One-Click"}
|
{HeaderId.ListUnsubscribePost.ToHeaderName(), "One-Click"}
|
||||||
}.ToFrozenDictionary());
|
}.ToFrozenDictionary());
|
||||||
|
|
|
@ -97,6 +97,7 @@ public enum Constants {
|
||||||
FileSystem.GetEmailTemplate("newsletter", DefaultTemplates["newsletter"]);
|
FileSystem.GetEmailTemplate("newsletter", DefaultTemplates["newsletter"]);
|
||||||
FileSystem.GetEmailTemplate("welcome", DefaultTemplates["welcome"]);
|
FileSystem.GetEmailTemplate("welcome", DefaultTemplates["welcome"]);
|
||||||
FileSystem.GetPartialTemplate("email-article", DefaultPartials["email-article"]);
|
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) {
|
public string ApplyTokens(string template, Func<string, string?> replacer) {
|
||||||
|
@ -132,7 +133,7 @@ public enum Constants {
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Process(string templateName, Dictionary<Constants, object?> data) {
|
public string Process(string templateName, Dictionary<Constants, object?> data) {
|
||||||
string template = ApplyTokens(GetTemplate(templateName), token =>
|
string template = ApplyTokens(GetTemplate(templateName), token =>
|
||||||
data.TryGetValue(Enum.Parse<Constants>(token, true), out object? v) ? v?.ToString() : null);
|
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>
|
<a href="{3}">Link</a>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email-plain-footer",
|
||||||
|
$"Unsubscribe: [[{Constants.EmailUnsubscribeLink}]]"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
|
@ -8,5 +8,6 @@ public interface IEmail {
|
||||||
string Subject { get; }
|
string Subject { get; }
|
||||||
string Title { get; }
|
string Title { get; }
|
||||||
string ContentHtml { get; }
|
string ContentHtml { get; }
|
||||||
|
string ContentPlain { get; }
|
||||||
FrozenDictionary<string, string> Headers { get; }
|
FrozenDictionary<string, string> Headers { get; }
|
||||||
}
|
}
|
|
@ -52,7 +52,10 @@ public class LiveEmailService(ILogger<LiveEmailService> logger, IOptions<EmailCo
|
||||||
To = { new MailboxAddress(email.ReceiverName, email.ReceiverEmail) },
|
To = { new MailboxAddress(email.ReceiverName, email.ReceiverEmail) },
|
||||||
Subject = email.Subject
|
Subject = email.Subject
|
||||||
};
|
};
|
||||||
var builder = new BodyBuilder { HtmlBody = email.ContentHtml };
|
var builder = new BodyBuilder {
|
||||||
|
HtmlBody = email.ContentHtml,
|
||||||
|
TextBody = email.ContentPlain
|
||||||
|
};
|
||||||
message.Body = builder.ToMessageBody();
|
message.Body = builder.ToMessageBody();
|
||||||
foreach ((string id, string value) in email.Headers) {
|
foreach ((string id, string value) in email.Headers) {
|
||||||
if (id == HeaderId.ListUnsubscribe.ToHeaderName()) {
|
if (id == HeaderId.ListUnsubscribe.ToHeaderName()) {
|
||||||
|
|
|
@ -55,8 +55,12 @@ public class NewsletterBackgroundService(ILogger<NewsletterBackgroundService> lo
|
||||||
last = subscribers.Last();
|
last = subscribers.Last();
|
||||||
|
|
||||||
foreach (var subscriber in subscribers) {
|
foreach (var subscriber in subscribers) {
|
||||||
var email = await factory.CreateSubscribedEmail(subscriber, articleLink, newsletter.Article.Title,
|
var email = await factory.CreateSubscribedEmail(subscriber, articleLink,
|
||||||
newsletter.Article.Title, newsletter.Article.BodyHtml, newsletter.Id.ToString(), replyTo);
|
newsletter.Article.Title,
|
||||||
|
newsletter.Article.Title,
|
||||||
|
newsletter.Article.BodyHtml,
|
||||||
|
newsletter.Article.BodyPlain,
|
||||||
|
"newsletter-" + newsletter.Id, replyTo);
|
||||||
await client.SendEmailAsync(email);
|
await client.SendEmailAsync(email);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Azure.Core;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Wave.Data;
|
using Wave.Data;
|
||||||
|
using Wave.Utilities;
|
||||||
|
|
||||||
namespace Wave.Services;
|
namespace Wave.Services;
|
||||||
|
|
||||||
|
@ -11,16 +13,19 @@ public class SmtpEmailSender(EmailFactory email, [FromKeyedServices("live")]IEma
|
||||||
#region IEmailSenderAsync<ApplicationUser>
|
#region IEmailSenderAsync<ApplicationUser>
|
||||||
|
|
||||||
public Task SendConfirmationLinkAsync(ApplicationUser user, string email, string confirmationLink) =>
|
public Task SendConfirmationLinkAsync(ApplicationUser user, string email, string confirmationLink) =>
|
||||||
SendEmailAsync(email, user.FullName, "Confirm your email",
|
SendDefaultMailAsync(email, user.FullName, "Confirm your email", "Confirm your email",
|
||||||
$"Please confirm your account by <a href='{confirmationLink}'>clicking here</a>.");
|
$"<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) =>
|
public Task SendPasswordResetLinkAsync(ApplicationUser user, string email, string resetLink) =>
|
||||||
SendEmailAsync(email, user.FullName, "Reset your password",
|
SendDefaultMailAsync(email, user.FullName, "Reset your password", "Reset your password",
|
||||||
$"Please reset your password by <a href='{resetLink}'>clicking here</a>.");
|
$"<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) =>
|
public Task SendPasswordResetCodeAsync(ApplicationUser user, string email, string resetCode) =>
|
||||||
SendEmailAsync(email, user.FullName, "Reset your password",
|
SendDefaultMailAsync(email, user.FullName, "Reset your password", "Reset your password",
|
||||||
$"Please reset your password using the following code: {resetCode}");
|
$"<p>Please reset your password using the following code: {resetCode}.</p>",
|
||||||
|
$"Please reset your password using the following code: {resetCode}.");
|
||||||
|
|
||||||
#endregion
|
#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) {
|
public async Task SendEmailAsync(string email, string? name, string subject, string htmlMessage) {
|
||||||
await EmailService.ConnectAsync(CancellationToken.None);
|
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);
|
await EmailService.DisconnectAsync(CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendDefaultMailAsync(string receiverMail, string? receiverName, string subject, string title, string bodyHtml) {
|
public async Task SendDefaultMailAsync(string receiverMail, string? receiverName, string subject, string title, string bodyHtml) {
|
||||||
await EmailService.ConnectAsync(CancellationToken.None);
|
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.SendEmailAsync(email);
|
||||||
await EmailService.DisconnectAsync(CancellationToken.None);
|
await EmailService.DisconnectAsync(CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendSubscribedMailAsync(EmailSubscriber subscriber, string subject, string title, string bodyHtml,
|
public async Task SendSubscribedMailAsync(EmailSubscriber subscriber, string subject, string title, string bodyHtml,
|
||||||
string browserUrl = "", string subscribedRole = "-1") {
|
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.ConnectAsync(CancellationToken.None);
|
||||||
await BulkEmailService.SendEmailAsync(email);
|
await BulkEmailService.SendEmailAsync(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendWelcomeMailAsync(EmailSubscriber subscriber, string subject, string title, string bodyHtml,
|
public async Task SendWelcomeMailAsync(EmailSubscriber subscriber, string subject, string title, string bodyHtml,
|
||||||
IEnumerable<EmailNewsletter> articles) {
|
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.ConnectAsync(CancellationToken.None);
|
||||||
await BulkEmailService.SendEmailAsync(email);
|
await BulkEmailService.SendEmailAsync(email);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
using System.Collections.Frozen;
|
using System.Collections.Frozen;
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Mjml.Net.Helpers;
|
|
||||||
|
|
||||||
namespace Wave.Services;
|
namespace Wave.Services;
|
||||||
|
|
||||||
public record StaticEmail(string ReceiverEmail, string? ReceiverName, string Subject, string Title, string ContentHtml,
|
public record StaticEmail(string ReceiverEmail, string? ReceiverName, string Subject, string Title, string ContentHtml, string ContentPlain, FrozenDictionary<string, string> Headers) : IEmail;
|
||||||
FrozenDictionary<string, string> Headers) : IEmail;
|
|
Loading…
Reference in a new issue