Draft: E-Mail templates

This commit is contained in:
Mia Rose Winter 2024-02-12 16:34:56 +01:00
parent aa5b7f04fc
commit 626aa841c7
Signed by: miawinter
GPG key ID: 4B6F6A83178F595E
3 changed files with 108 additions and 13 deletions

View file

@ -128,6 +128,7 @@
logMessages.Add("No Email provider configured."); logMessages.Add("No Email provider configured.");
} }
builder.Services.AddSingleton<EmailTemplateService>();
builder.Services.AddHostedService<EmailBackgroundWorker>(); builder.Services.AddHostedService<EmailBackgroundWorker>();
#endregion #endregion

View file

@ -9,12 +9,13 @@
namespace Wave.Services; namespace Wave.Services;
public class EmailBackgroundWorker(ILogger<EmailBackgroundWorker> logger, IDbContextFactory<ApplicationDbContext> contextFactory, IOptions<SmtpConfiguration> config, IOptions<Customization> customizations, IOptions<Features> features) : IHostedService, IDisposable { public class EmailBackgroundWorker(ILogger<EmailBackgroundWorker> logger, IDbContextFactory<ApplicationDbContext> contextFactory, IOptions<SmtpConfiguration> config, IOptions<Customization> customizations, IOptions<Features> features, EmailTemplateService templateService) : IHostedService, IDisposable {
private ILogger<EmailBackgroundWorker> Logger { get; } = logger; private ILogger<EmailBackgroundWorker> Logger { get; } = logger;
private IDbContextFactory<ApplicationDbContext> ContextFactory { get; } = contextFactory; private IDbContextFactory<ApplicationDbContext> ContextFactory { get; } = contextFactory;
private SmtpConfiguration Configuration { get; } = config.Value; private SmtpConfiguration Configuration { get; } = config.Value;
private Customization Customizations { get; } = customizations.Value; private Customization Customizations { get; } = customizations.Value;
private Features Features { get; } = features.Value; private Features Features { get; } = features.Value;
private EmailTemplateService TemplateService { get; } = templateService;
private Timer? Timer { get; set; } private Timer? Timer { get; set; }
@ -84,22 +85,19 @@ public class EmailBackgroundWorker(ILogger<EmailBackgroundWorker> logger, IDbCon
string articleLink = ArticleUtilities.GenerateArticleLink( string articleLink = ArticleUtilities.GenerateArticleLink(
newsletter.Article, new Uri(Customizations.AppUrl, UriKind.Absolute)); newsletter.Article, new Uri(Customizations.AppUrl, UriKind.Absolute));
string unsubscribeLink = new Uri(new Uri(Customizations.AppUrl, UriKind.Absolute), "/unsubscribe").AbsoluteUri; string unsubscribeLink = new Uri(new Uri(Customizations.AppUrl, UriKind.Absolute), "/unsubscribe").AbsoluteUri;
string template = $""" string template = TemplateService.Process("newsletter", new Dictionary<EmailTemplateService.Constants, object?>{
<mjml><mj-body><mj-section><mj-column> {EmailTemplateService.Constants.BrowserLink, articleLink},
<mj-text align="center"><a href="{articleLink}">Read in Browser</a></mj-text> {EmailTemplateService.Constants.ContentLogo, "https://blog.winter-software.com/img/logo.png"},
<mj-image width="200px" src="https://blog.winter-software.com/img/logo.png"></mj-image> {EmailTemplateService.Constants.ContentTitle, newsletter.Article.Title},
<mj-divider border-width="1px"></mj-divider> {EmailTemplateService.Constants.ContentBody, newsletter.Article.BodyHtml},
<mj-text>{newsletter.Article.BodyHtml}</mj-text> {EmailTemplateService.Constants.EmailUnsubscribeLink, unsubscribeLink}
<mj-divider border-width="1px"></mj-divider> });
<mj-text align="center"><a href="{unsubscribeLink}">Unsubscribe</a></mj-text>
</mj-column></mj-section></mj-body></mjml>
""";
var message = new MimeMessage { var message = new MimeMessage {
From = { sender }, From = { sender },
To = { },
Subject = newsletter.Article.Title Subject = newsletter.Article.Title
}; };
var builder = new BodyBuilder() { var builder = new BodyBuilder {
HtmlBody = mjmlRenderer.Render(template, options).Html HtmlBody = mjmlRenderer.Render(template, options).Html
}; };
message.Body = builder.ToMessageBody(); message.Body = builder.ToMessageBody();

View file

@ -0,0 +1,96 @@
using System.Text.RegularExpressions;
using Mjml.Net;
namespace Wave.Services;
public partial class EmailTemplateService(ILogger<EmailTemplateService> logger) {
public enum Constants {
BrowserLink, HomeLink, ContentLogo, ContentTitle, ContentBody, EmailUnsubscribeLink
}
private ILogger<EmailTemplateService> Logger { get; } = logger;
private IMjmlRenderer Renderer { get; } = new MjmlRenderer();
private Regex TokenMatcher { get; } = MyRegex();
public string Default(string url, string logoLink, string title, string body) {
return Process("default", new Dictionary<Constants, object?> {
{Constants.HomeLink, url},
{Constants.ContentLogo, logoLink},
{Constants.ContentTitle, title},
{Constants.ContentBody, body}
});
}
public string Process(string templateName, Dictionary<Constants, object?> data) {
var options = new MjmlOptions {
Beautify = false
};
string template = $"""
<mjml>
<mj-head>
<mj-preview/>
</mj-head>
<mj-body>
<mj-section>
<mj-column>
<mj-text align="center" font-size="13px" font-family="Ubuntu,Verdana">
<a href="[[{Constants.BrowserLink}]]">Read in Browser</a></mj-text>
</mj-column>
</mj-section>
<mj-section direction="rtl" padding-bottom="15px" padding-left="0px" padding-right="0px" padding-top="15px" padding="15px 0px 15px 0px">
<mj-column vertical-align="middle" width="33%">
<mj-image align="center" alt="" border-radius="0" border="none" container-background-color="transparent" height="auto" padding-bottom="5px" padding-left="5px" padding-right="5px" padding-top="5px" padding="5px 5px 5px 5px" href="[[{Constants.HomeLink}]]" src="[[{Constants.ContentLogo}]]"></mj-image>
</mj-column>
<mj-column vertical-align="middle" width="67%">
<mj-text font-size="13px" font-family="Ubuntu,Verdana">
<h1>[[{Constants.ContentTitle}]]</h1>
</mj-text>
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-divider border-width="1px"></mj-divider>
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-text color="#55575d" font-size="13px" font-family="Ubuntu,Verdana">[[{Constants.ContentBody}]]</mj-text>
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-divider border-width="1px"></mj-divider>
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-text align="center" font-size="13px" font-family="Ubuntu,Verdana">
<a href="[[{Constants.EmailUnsubscribeLink}]]">Unsubscribe</a>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
""";
template = TokenMatcher.Replace(template, t =>
data.TryGetValue(Enum.Parse<Constants>(t.Value[2..^2], true), out object? v) ?
v?.ToString() ?? "" :
"");
(string html, var errors) = Renderer.Render(template, options);
foreach (var error in errors) {
Logger.LogWarning("Validation error in template {template}: [{position}] [{type}] {error}",
templateName, error.Position, error.Type, error.Error);
}
return html;
}
[GeneratedRegex(@"(\[\[.*?\]\])",
RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.CultureInvariant)]
private static partial Regex MyRegex();
}