From 626aa841c7d4d6bca50a4829587872114e1caaa4 Mon Sep 17 00:00:00 2001 From: Mia Winter Date: Mon, 12 Feb 2024 16:34:56 +0100 Subject: [PATCH] Draft: E-Mail templates --- Wave/Program.cs | 1 + Wave/Services/EmailBackgroundWorker.cs | 24 +++---- Wave/Services/EmailTemplateService.cs | 96 ++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 13 deletions(-) create mode 100644 Wave/Services/EmailTemplateService.cs diff --git a/Wave/Program.cs b/Wave/Program.cs index cce534a..2358d34 100644 --- a/Wave/Program.cs +++ b/Wave/Program.cs @@ -128,6 +128,7 @@ logMessages.Add("No Email provider configured."); } +builder.Services.AddSingleton(); builder.Services.AddHostedService(); #endregion diff --git a/Wave/Services/EmailBackgroundWorker.cs b/Wave/Services/EmailBackgroundWorker.cs index ab0d350..e9dce56 100644 --- a/Wave/Services/EmailBackgroundWorker.cs +++ b/Wave/Services/EmailBackgroundWorker.cs @@ -9,12 +9,13 @@ namespace Wave.Services; -public class EmailBackgroundWorker(ILogger logger, IDbContextFactory contextFactory, IOptions config, IOptions customizations, IOptions features) : IHostedService, IDisposable { +public class EmailBackgroundWorker(ILogger logger, IDbContextFactory contextFactory, IOptions config, IOptions customizations, IOptions features, EmailTemplateService templateService) : IHostedService, IDisposable { private ILogger Logger { get; } = logger; private IDbContextFactory ContextFactory { get; } = contextFactory; private SmtpConfiguration Configuration { get; } = config.Value; private Customization Customizations { get; } = customizations.Value; private Features Features { get; } = features.Value; + private EmailTemplateService TemplateService { get; } = templateService; private Timer? Timer { get; set; } @@ -84,22 +85,19 @@ public class EmailBackgroundWorker(ILogger logger, IDbCon string articleLink = ArticleUtilities.GenerateArticleLink( newsletter.Article, new Uri(Customizations.AppUrl, UriKind.Absolute)); string unsubscribeLink = new Uri(new Uri(Customizations.AppUrl, UriKind.Absolute), "/unsubscribe").AbsoluteUri; - string template = $""" - - Read in Browser - - - {newsletter.Article.BodyHtml} - - Unsubscribe - - """; + string template = TemplateService.Process("newsletter", new Dictionary{ + {EmailTemplateService.Constants.BrowserLink, articleLink}, + {EmailTemplateService.Constants.ContentLogo, "https://blog.winter-software.com/img/logo.png"}, + {EmailTemplateService.Constants.ContentTitle, newsletter.Article.Title}, + {EmailTemplateService.Constants.ContentBody, newsletter.Article.BodyHtml}, + {EmailTemplateService.Constants.EmailUnsubscribeLink, unsubscribeLink} + }); + var message = new MimeMessage { From = { sender }, - To = { }, Subject = newsletter.Article.Title }; - var builder = new BodyBuilder() { + var builder = new BodyBuilder { HtmlBody = mjmlRenderer.Render(template, options).Html }; message.Body = builder.ToMessageBody(); diff --git a/Wave/Services/EmailTemplateService.cs b/Wave/Services/EmailTemplateService.cs new file mode 100644 index 0000000..97b72ca --- /dev/null +++ b/Wave/Services/EmailTemplateService.cs @@ -0,0 +1,96 @@ +using System.Text.RegularExpressions; +using Mjml.Net; + +namespace Wave.Services; + +public partial class EmailTemplateService(ILogger logger) { + public enum Constants { + BrowserLink, HomeLink, ContentLogo, ContentTitle, ContentBody, EmailUnsubscribeLink + } + + private ILogger 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.HomeLink, url}, + {Constants.ContentLogo, logoLink}, + {Constants.ContentTitle, title}, + {Constants.ContentBody, body} + }); + } + + public string Process(string templateName, Dictionary data) { + var options = new MjmlOptions { + Beautify = false + }; + + string template = $""" + + + + + + + + + Read in Browser + + + + + + + + +

[[{Constants.ContentTitle}]]

+
+
+
+ + + + + + + + [[{Constants.ContentBody}]] + + + + + + + + + + + Unsubscribe + + + +
+
+ """; + + template = TokenMatcher.Replace(template, t => + data.TryGetValue(Enum.Parse(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(); +} \ No newline at end of file