💄 Optimized landing email

This commit is contained in:
2025-05-17 17:36:34 +08:00
parent d3b56b741e
commit 6728bd5607
8 changed files with 132 additions and 169 deletions

View File

@ -62,19 +62,45 @@ public class EmailService
await client.DisconnectAsync(true);
}
private static string _ConvertHtmlToPlainText(string html)
{
// Remove style tags and their contents
html = System.Text.RegularExpressions.Regex.Replace(html, "<style[^>]*>.*?</style>", "",
System.Text.RegularExpressions.RegexOptions.Singleline);
// Replace header tags with text + newlines
html = System.Text.RegularExpressions.Regex.Replace(html, "<h[1-6][^>]*>(.*?)</h[1-6]>", "$1\n\n",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
// Replace line breaks
html = html.Replace("<br>", "\n").Replace("<br/>", "\n").Replace("<br />", "\n");
// Remove all remaining HTML tags
html = System.Text.RegularExpressions.Regex.Replace(html, "<[^>]+>", "");
// Decode HTML entities
html = System.Net.WebUtility.HtmlDecode(html);
// Remove excess whitespace
html = System.Text.RegularExpressions.Regex.Replace(html, @"\s+", " ").Trim();
return html;
}
public async Task SendTemplatedEmailAsync<TComponent, TModel>(string? recipientName, string recipientEmail,
string subject, TModel model, string fallbackTextBody)
string subject, TModel model)
where TComponent : IComponent
{
try
{
var htmlBody = await _viewRenderer.RenderComponentToStringAsync<TComponent, TModel>(model);
var fallbackTextBody = _ConvertHtmlToPlainText(htmlBody);
await SendEmailAsync(recipientName, recipientEmail, subject, fallbackTextBody, htmlBody);
}
catch (Exception err)
{
_logger.LogError(err, "Failed to render email template...");
await SendEmailAsync(recipientName, recipientEmail, subject, fallbackTextBody);
throw;
}
}
}

View File

@ -17,23 +17,21 @@ public class RazorViewRenderer(
ILogger<RazorViewRenderer> logger
)
{
public async Task<string> RenderComponentToStringAsync<TComponent, TModel>(TModel model)
public async Task<string> RenderComponentToStringAsync<TComponent, TModel>(TModel? model)
where TComponent : IComponent
{
await using var htmlRenderer = new HtmlRenderer(serviceProvider, loggerFactory);
var viewDictionary = new ViewDataDictionary<TModel>(
new EmptyModelMetadataProvider(),
new ModelStateDictionary())
{
Model = model
};
return await htmlRenderer.Dispatcher.InvokeAsync(async () =>
{
try
{
var parameterView = ParameterView.FromDictionary(viewDictionary);
var dictionary = model?.GetType().GetProperties()
.ToDictionary(
prop => prop.Name,
prop => prop.GetValue(model, null)
) ?? new Dictionary<string, object?>();
var parameterView = ParameterView.FromDictionary(dictionary);
var output = await htmlRenderer.RenderComponentAsync<TComponent>(parameterView);
return output.ToHtmlString();
}

View File

@ -44,9 +44,9 @@ public class MagicSpellService(AppDatabase db, EmailService email, IConfiguratio
.FirstOrDefaultAsync();
if (contact is null) throw new ArgumentException("Account has no contact method that can use");
var link = $"${configuration.GetValue<string>("BaseUrl")}/spells/{Uri.EscapeDataString(spell.Spell)}";
var link = $"{configuration.GetValue<string>("BaseUrl")}/spells/{Uri.EscapeDataString(spell.Spell)}";
logger.LogInformation($"Sending magic spell... {link}");
logger.LogInformation("Sending magic spell... {Link}", link);
try
{
@ -61,8 +61,7 @@ public class MagicSpellService(AppDatabase db, EmailService email, IConfiguratio
{
Name = contact.Account.Name,
VerificationLink = link
},
$"Thank you for creating an account.\nFor accessing all the features, confirm your registration with the link below:\n\n{link}"
}
);
break;
default: