Implemented welcome email on signup, Improved unsubscribe mail handling

This commit is contained in:
Mia Rose Winter 2024-02-13 23:41:14 +01:00
parent 84986065bf
commit 735f564a64
Signed by: miawinter
GPG key ID: 4B6F6A83178F595E
7 changed files with 106 additions and 40 deletions

View file

@ -7,6 +7,7 @@
@using System.Net @using System.Net
@using Microsoft.EntityFrameworkCore @using Microsoft.EntityFrameworkCore
@using Wave.Services @using Wave.Services
@using Wave.Utilities
@inject ILogger<EmailSignup> Logger @inject ILogger<EmailSignup> Logger
@inject IStringLocalizer<EmailSignup> Localizer @inject IStringLocalizer<EmailSignup> Localizer
@ -63,7 +64,7 @@
if (Id is null || Token is null) return; if (Id is null || Token is null) return;
try { try {
var id = await TemplateService.ValidateTokensAsync(Id, Token); var id = await TemplateService.ValidateTokensAsync(Id, Token, deleteToken: false);
if (id is null) { if (id is null) {
Message = Localizer["Failure_Message"]; Message = Localizer["Failure_Message"];
@ -78,11 +79,30 @@
} }
subscriber.Unsubscribed = false; subscriber.Unsubscribed = false;
await context.SaveChangesAsync(); await context.SaveChangesAsync();
await TemplateService.ValidateTokensAsync(Id, Token, deleteToken: true);
Message = Localizer["Success_Message"]; Message = Localizer["Success_Message"];
await TemplateService.ValidateTokensAsync(Id, Token, deleteToken: true);
var articles = await context.Set<EmailNewsletter>()
.IgnoreAutoIncludes()
.Include(a => a.Article).ThenInclude(a => a.Author)
.OrderByDescending(a => a.DistributionDateTime)
.Take(3)
.ToListAsync();
string body = $"<p>{Localizer["WelcomeEmailBody"]}</p>\n";
foreach (var n in articles) {
string articleLink = ArticleUtilities.GenerateArticleLink(n.Article, new Uri(Customizations.Value.AppUrl, UriKind.Absolute));
body = body +
"<div style=\"padding: 10px; background: #333; color: #fff; margin-bottom: 10px\">" +
$"<h3>{n.Article.Title}</h3>" +
$"<small>{n.Article.Author.Name}</small>" +
$"<p>{n.Article.Body[..Math.Min(100, n.Article.Body.Length)]}...</p>" +
$"<a href=\"{articleLink}\">Link</a>" +
"</div>";
}
await EmailSender.SendSubscribedMailAsync(subscriber,
Localizer["WelcomeEmailSubject"], Localizer["WelcomeEmailTitle"], body, subscribedRole: "welcome");
} catch (Exception ex) { } catch (Exception ex) {
Logger.LogError(ex, "Error trying to confirm subscriber."); Logger.LogError(ex, "Error trying to confirm subscriber.");
Message = Localizer["Failure_Message"]; Message = Localizer["Failure_Message"];

View file

@ -15,7 +15,7 @@ public class ApplicationUser : IdentityUser {
public string Biography { get; set; } = string.Empty; public string Biography { get; set; } = string.Empty;
public string BiographyHtml { get; set; } = string.Empty; public string BiographyHtml { get; set; } = string.Empty;
public string Name => FullName ?? UserName ?? "Anon"; public string Name => FullName ?? "Guest Author";
public IList<Article> Articles { get; set; } = []; public IList<Article> Articles { get; set; } = [];
public IList<UserLink> Links { get; set; } = []; public IList<UserLink> Links { get; set; } = [];

View file

@ -128,4 +128,13 @@
<data name="Success_Message" xml:space="preserve"> <data name="Success_Message" xml:space="preserve">
<value>Ihre E-Mail wurde erfolgreich bestätigt. Sie werden nun in zukunft Benachrichtigungen zu neuen Artikeln erhalten.</value> <value>Ihre E-Mail wurde erfolgreich bestätigt. Sie werden nun in zukunft Benachrichtigungen zu neuen Artikeln erhalten.</value>
</data> </data>
<data name="WelcomeEmailTitle" xml:space="preserve">
<value>Sie sind nun Abonniert!</value>
</data>
<data name="WelcomeEmailSubject" xml:space="preserve">
<value>Ihr Newsletter-Abonnement wurde Bestätigt</value>
</data>
<data name="WelcomeEmailBody" xml:space="preserve">
<value>Sie werden von nun an über neue Artikel informiert. Schauen Sie doch mal in der folgenden Sektion was Sie vielleicht verpasst haben. Sie können sich jeder Zeit in diesem oder zukünftigen E-Mails abmelden über den Link den Sie am ende finden.</value>
</data>
</root> </root>

View file

@ -134,4 +134,13 @@
<data name="Success_Message" xml:space="preserve"> <data name="Success_Message" xml:space="preserve">
<value>Your Email has been confirmed successfully. You will not receive notifications about future articles.</value> <value>Your Email has been confirmed successfully. You will not receive notifications about future articles.</value>
</data> </data>
<data name="WelcomeEmailSubject" xml:space="preserve">
<value>Your Newsletter Subscription has been Confirmed</value>
</data>
<data name="WelcomeEmailTitle" xml:space="preserve">
<value>You are now subscribed!</value>
</data>
<data name="WelcomeEmailBody" xml:space="preserve">
<value>You are now being notified about new articles coming out. Have a look at the following section for what you might have missed. You can always unsubscribe using the link at the bottom of this or future Emails.</value>
</data>
</root> </root>

View file

@ -114,6 +114,7 @@ public class EmailBackgroundWorker(ILogger<EmailBackgroundWorker> logger, IDbCon
// TODO mailto: unsubscribe: // TODO mailto: unsubscribe:
// List-Unsubscribe: <mailto: unsubscribe@example.com?subject=unsubscribe>, <http://www.example.com/unsubscribe.html> // List-Unsubscribe: <mailto: unsubscribe@example.com?subject=unsubscribe>, <http://www.example.com/unsubscribe.html>
message.Headers.Add(HeaderId.ListUnsubscribe, $"<{unsubscribeLink}>"); message.Headers.Add(HeaderId.ListUnsubscribe, $"<{unsubscribeLink}>");
message.Headers.Add(HeaderId.ListUnsubscribePost, "One-Click");
message.To.Add(new MailboxAddress(subscriber.Name, subscriber.Email)); message.To.Add(new MailboxAddress(subscriber.Name, subscriber.Email));
message.Body = builder.ToMessageBody(); message.Body = builder.ToMessageBody();
client.Send(message); client.Send(message);

View file

@ -1,8 +1,11 @@
using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Identity.UI.Services;
using Wave.Data;
namespace Wave.Services; namespace Wave.Services;
public interface IAdvancedEmailSender : IEmailSender { public interface IAdvancedEmailSender : IEmailSender {
Task SendEmailAsync(string email, string? name, string subject, string htmlMessage); Task SendEmailAsync(string email, string? name, string subject, string htmlMessage);
Task SendDefaultMailAsync(string receiverMail, string? receiverName, string subject, string title, string bodyHtml); Task SendDefaultMailAsync(string receiverMail, string? receiverName, string subject, string title, string bodyHtml);
Task SendSubscribedMailAsync(EmailSubscriber subscriber, string subject, string title, string bodyHtml,
string browserUrl = "", string subscribedRole = "-1");
} }

View file

@ -1,4 +1,5 @@
using MailKit.Net.Smtp; using System.Net;
using MailKit.Net.Smtp;
using MailKit.Security; using MailKit.Security;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -31,7 +32,9 @@ public class SmtpEmailSender(ILogger<SmtpEmailSender> logger, IOptions<SmtpConfi
return SendEmailAsync(email, null, subject, htmlMessage); return SendEmailAsync(email, null, subject, htmlMessage);
} }
public async Task SendEmailAsync(string email, string? name, string subject, string htmlMessage) { public Task SendEmailAsync(string email, string? name, string subject, string htmlMessage)
=> SendEmailAsync(email, name, subject, htmlMessage, []);
public async Task SendEmailAsync(string email, string? name, string subject, string htmlMessage, params Header[] header) {
try { try {
var message = new MimeMessage { var message = new MimeMessage {
From = {new MailboxAddress(Configuration.SenderName, Configuration.SenderEmail)}, From = {new MailboxAddress(Configuration.SenderName, Configuration.SenderEmail)},
@ -44,6 +47,9 @@ public class SmtpEmailSender(ILogger<SmtpEmailSender> logger, IOptions<SmtpConfi
}; };
message.Body = builder.ToMessageBody(); message.Body = builder.ToMessageBody();
foreach (var h in header) {
message.Headers.Add(h);
}
using var client = new SmtpClient(); using var client = new SmtpClient();
await client.ConnectAsync(Configuration.Host, Configuration.Port, await client.ConnectAsync(Configuration.Host, Configuration.Port,
@ -66,13 +72,31 @@ public class SmtpEmailSender(ILogger<SmtpEmailSender> logger, IOptions<SmtpConfi
} }
public Task SendDefaultMailAsync(string receiverMail, string? receiverName, string subject, string title, string bodyHtml) { public Task SendDefaultMailAsync(string receiverMail, string? receiverName, string subject, string title, string bodyHtml) {
var host = new Uri(string.IsNullOrWhiteSpace(Customizations.AppUrl) ? "" : Customizations.AppUrl); var host = new Uri(string.IsNullOrWhiteSpace(Customizations.AppUrl) ? "" : Customizations.AppUrl); // TODO get link
string logo = !string.IsNullOrWhiteSpace(Customizations.LogoLink) string logo = !string.IsNullOrWhiteSpace(Customizations.LogoLink)
? Customizations.LogoLink ? Customizations.LogoLink
: new Uri(host, "/img/logo.png").AbsoluteUri; : new Uri(host, "/img/logo.png").AbsoluteUri;
string body = TemplateService.Default(host.AbsoluteUri, logo, title, bodyHtml); string body = TemplateService.Default(host.AbsoluteUri, logo, title, bodyHtml);
return SendEmailAsync(receiverMail, receiverName, subject, body); return SendEmailAsync(receiverMail, receiverName, subject, body);
} }
public async Task SendSubscribedMailAsync(EmailSubscriber subscriber, string subject, string title, string bodyHtml,
string browserUrl = "", string subscribedRole = "-1") {
(string user, string token) = await TemplateService
.CreateConfirmTokensAsync(subscriber.Id, "unsubscribe-"+subscribedRole, TimeSpan.FromDays(30));
var host = new Uri(string.IsNullOrWhiteSpace(Customizations.AppUrl) ? "" : Customizations.AppUrl); // TODO get link
browserUrl = string.IsNullOrWhiteSpace(browserUrl) ? host.AbsoluteUri : browserUrl; // TODO find better solution
string logo = !string.IsNullOrWhiteSpace(Customizations.LogoLink)
? Customizations.LogoLink
: new Uri(host, "/img/logo.png").AbsoluteUri;
string unsubscribeLink = new Uri(host,
$"/Email/Unsubscribe?newsletter={subscribedRole}&user={WebUtility.UrlEncode(user)}&token={WebUtility.UrlEncode(token)}").AbsoluteUri;
string body = TemplateService.Newsletter(host.AbsoluteUri, browserUrl, logo, title, bodyHtml, unsubscribeLink);
await SendEmailAsync(subscriber.Email, subscriber.Name, subject, body,
new Header(HeaderId.ListUnsubscribe, $"<{unsubscribeLink}>"),
new Header(HeaderId.ListUnsubscribePost, "One-Click"));
}
} }
public class EmailNotSendException(string message, Exception exception) : ApplicationException(message, exception); public class EmailNotSendException(string message, Exception exception) : ApplicationException(message, exception);