Draft: E-Mail templates
This commit is contained in:
parent
aa5b7f04fc
commit
626aa841c7
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
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