Draft: E-Mail templates
This commit is contained in:
parent
aa5b7f04fc
commit
626aa841c7
|
@ -128,6 +128,7 @@
|
|||
logMessages.Add("No Email provider configured.");
|
||||
}
|
||||
|
||||
builder.Services.AddSingleton<EmailTemplateService>();
|
||||
builder.Services.AddHostedService<EmailBackgroundWorker>();
|
||||
|
||||
#endregion
|
||||
|
|
|
@ -9,12 +9,13 @@
|
|||
|
||||
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 IDbContextFactory<ApplicationDbContext> 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<EmailBackgroundWorker> 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 = $"""
|
||||
<mjml><mj-body><mj-section><mj-column>
|
||||
<mj-text align="center"><a href="{articleLink}">Read in Browser</a></mj-text>
|
||||
<mj-image width="200px" src="https://blog.winter-software.com/img/logo.png"></mj-image>
|
||||
<mj-divider border-width="1px"></mj-divider>
|
||||
<mj-text>{newsletter.Article.BodyHtml}</mj-text>
|
||||
<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>
|
||||
""";
|
||||
string template = TemplateService.Process("newsletter", new Dictionary<EmailTemplateService.Constants, object?>{
|
||||
{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();
|
||||
|
|
96
Wave/Services/EmailTemplateService.cs
Normal file
96
Wave/Services/EmailTemplateService.cs
Normal 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();
|
||||
}
|
Loading…
Reference in a new issue