From 735f564a641fd90c3811a9d22beb4c989c326eb8 Mon Sep 17 00:00:00 2001 From: Mia Winter Date: Tue, 13 Feb 2024 23:41:14 +0100 Subject: [PATCH] Implemented welcome email on signup, Improved unsubscribe mail handling --- Wave/Components/Pages/EmailSignup.razor | 26 ++++- Wave/Data/ApplicationUser.cs | 2 +- .../Components/Pages/EmailSignup.de-DE.resx | 9 ++ .../Components/Pages/EmailSignup.resx | 9 ++ Wave/Services/EmailBackgroundWorker.cs | 1 + ...EmailSender.cs => IAdvancedEmailSender.cs} | 3 + Wave/Services/SmtpEmailSender.cs | 96 ++++++++++++------- 7 files changed, 106 insertions(+), 40 deletions(-) rename Wave/Services/{AdvancedEmailSender.cs => IAdvancedEmailSender.cs} (65%) diff --git a/Wave/Components/Pages/EmailSignup.razor b/Wave/Components/Pages/EmailSignup.razor index c369e4c..6b33ebd 100644 --- a/Wave/Components/Pages/EmailSignup.razor +++ b/Wave/Components/Pages/EmailSignup.razor @@ -7,6 +7,7 @@ @using System.Net @using Microsoft.EntityFrameworkCore @using Wave.Services +@using Wave.Utilities @inject ILogger Logger @inject IStringLocalizer Localizer @@ -63,7 +64,7 @@ if (Id is null || Token is null) return; try { - var id = await TemplateService.ValidateTokensAsync(Id, Token); + var id = await TemplateService.ValidateTokensAsync(Id, Token, deleteToken: false); if (id is null) { Message = Localizer["Failure_Message"]; @@ -78,11 +79,30 @@ } subscriber.Unsubscribed = false; await context.SaveChangesAsync(); - - await TemplateService.ValidateTokensAsync(Id, Token, deleteToken: true); Message = Localizer["Success_Message"]; + await TemplateService.ValidateTokensAsync(Id, Token, deleteToken: true); + var articles = await context.Set() + .IgnoreAutoIncludes() + .Include(a => a.Article).ThenInclude(a => a.Author) + .OrderByDescending(a => a.DistributionDateTime) + .Take(3) + .ToListAsync(); + string body = $"

{Localizer["WelcomeEmailBody"]}

\n"; + + foreach (var n in articles) { + string articleLink = ArticleUtilities.GenerateArticleLink(n.Article, new Uri(Customizations.Value.AppUrl, UriKind.Absolute)); + body = body + + "
" + + $"

{n.Article.Title}

" + + $"{n.Article.Author.Name}" + + $"

{n.Article.Body[..Math.Min(100, n.Article.Body.Length)]}...

" + + $"Link" + + "
"; + } + await EmailSender.SendSubscribedMailAsync(subscriber, + Localizer["WelcomeEmailSubject"], Localizer["WelcomeEmailTitle"], body, subscribedRole: "welcome"); } catch (Exception ex) { Logger.LogError(ex, "Error trying to confirm subscriber."); Message = Localizer["Failure_Message"]; diff --git a/Wave/Data/ApplicationUser.cs b/Wave/Data/ApplicationUser.cs index 003432d..1bcfb97 100644 --- a/Wave/Data/ApplicationUser.cs +++ b/Wave/Data/ApplicationUser.cs @@ -15,7 +15,7 @@ public class ApplicationUser : IdentityUser { public string Biography { 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
Articles { get; set; } = []; public IList Links { get; set; } = []; diff --git a/Wave/Resources/Components/Pages/EmailSignup.de-DE.resx b/Wave/Resources/Components/Pages/EmailSignup.de-DE.resx index 9c061dd..afa9cec 100644 --- a/Wave/Resources/Components/Pages/EmailSignup.de-DE.resx +++ b/Wave/Resources/Components/Pages/EmailSignup.de-DE.resx @@ -128,4 +128,13 @@ Ihre E-Mail wurde erfolgreich bestätigt. Sie werden nun in zukunft Benachrichtigungen zu neuen Artikeln erhalten. + + Sie sind nun Abonniert! + + + Ihr Newsletter-Abonnement wurde Bestätigt + + + 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. + \ No newline at end of file diff --git a/Wave/Resources/Components/Pages/EmailSignup.resx b/Wave/Resources/Components/Pages/EmailSignup.resx index 21e7bc3..25b2c72 100644 --- a/Wave/Resources/Components/Pages/EmailSignup.resx +++ b/Wave/Resources/Components/Pages/EmailSignup.resx @@ -134,4 +134,13 @@ Your Email has been confirmed successfully. You will not receive notifications about future articles. + + Your Newsletter Subscription has been Confirmed + + + You are now subscribed! + + + 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. + \ No newline at end of file diff --git a/Wave/Services/EmailBackgroundWorker.cs b/Wave/Services/EmailBackgroundWorker.cs index e2b022c..8cd907d 100644 --- a/Wave/Services/EmailBackgroundWorker.cs +++ b/Wave/Services/EmailBackgroundWorker.cs @@ -114,6 +114,7 @@ public class EmailBackgroundWorker(ILogger logger, IDbCon // TODO mailto: unsubscribe: // List-Unsubscribe: , message.Headers.Add(HeaderId.ListUnsubscribe, $"<{unsubscribeLink}>"); + message.Headers.Add(HeaderId.ListUnsubscribePost, "One-Click"); message.To.Add(new MailboxAddress(subscriber.Name, subscriber.Email)); message.Body = builder.ToMessageBody(); client.Send(message); diff --git a/Wave/Services/AdvancedEmailSender.cs b/Wave/Services/IAdvancedEmailSender.cs similarity index 65% rename from Wave/Services/AdvancedEmailSender.cs rename to Wave/Services/IAdvancedEmailSender.cs index c763dc8..852cd9a 100644 --- a/Wave/Services/AdvancedEmailSender.cs +++ b/Wave/Services/IAdvancedEmailSender.cs @@ -1,8 +1,11 @@ using Microsoft.AspNetCore.Identity.UI.Services; +using Wave.Data; namespace Wave.Services; public interface IAdvancedEmailSender : IEmailSender { Task SendEmailAsync(string email, string? name, string subject, string htmlMessage); 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"); } \ No newline at end of file diff --git a/Wave/Services/SmtpEmailSender.cs b/Wave/Services/SmtpEmailSender.cs index 8b53662..0229d45 100644 --- a/Wave/Services/SmtpEmailSender.cs +++ b/Wave/Services/SmtpEmailSender.cs @@ -1,4 +1,5 @@ -using MailKit.Net.Smtp; +using System.Net; +using MailKit.Net.Smtp; using MailKit.Security; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; @@ -10,45 +11,50 @@ namespace Wave.Services; public class SmtpEmailSender(ILogger logger, IOptions config, IOptions customizations, EmailTemplateService templateService) : IEmailSender, IAdvancedEmailSender { private ILogger Logger { get; } = logger; - private SmtpConfiguration Configuration { get; } = config.Value; + private SmtpConfiguration Configuration { get; } = config.Value; private Customization Customizations { get; } = customizations.Value; - private EmailTemplateService TemplateService { get; } = templateService; + private EmailTemplateService TemplateService { get; } = templateService; public Task SendConfirmationLinkAsync(ApplicationUser user, string email, string confirmationLink) => - SendEmailAsync(email, "Confirm your email", - $"Please confirm your account by clicking here."); + SendEmailAsync(email, "Confirm your email", + $"Please confirm your account by clicking here."); - public Task SendPasswordResetLinkAsync(ApplicationUser user, string email, string resetLink) => - SendEmailAsync(email, "Reset your password", - $"Please reset your password by clicking here."); + public Task SendPasswordResetLinkAsync(ApplicationUser user, string email, string resetLink) => + SendEmailAsync(email, "Reset your password", + $"Please reset your password by clicking here."); - public Task SendPasswordResetCodeAsync(ApplicationUser user, string email, string resetCode) => - SendEmailAsync(email, "Reset your password", - $"Please reset your password using the following code: {resetCode}"); + public Task SendPasswordResetCodeAsync(ApplicationUser user, string email, string resetCode) => + SendEmailAsync(email, "Reset your password", + $"Please reset your password using the following code: {resetCode}"); - public Task SendEmailAsync(string email, string subject, string htmlMessage) { - return SendEmailAsync(email, null, subject, htmlMessage); - } + public Task SendEmailAsync(string email, string subject, string htmlMessage) { + return SendEmailAsync(email, null, subject, htmlMessage); + } - public async Task SendEmailAsync(string email, string? name, string subject, string htmlMessage) { - try { - var message = new MimeMessage { - From = {new MailboxAddress(Configuration.SenderName, Configuration.SenderEmail)}, - To = { new MailboxAddress(name, email) }, - Subject = subject - }; + 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 { + var message = new MimeMessage { + From = {new MailboxAddress(Configuration.SenderName, Configuration.SenderEmail)}, + To = { new MailboxAddress(name, email) }, + Subject = subject + }; - var builder = new BodyBuilder { - HtmlBody = htmlMessage - }; + var builder = new BodyBuilder { + HtmlBody = htmlMessage + }; - message.Body = builder.ToMessageBody(); + message.Body = builder.ToMessageBody(); + foreach (var h in header) { + message.Headers.Add(h); + } - using var client = new SmtpClient(); - await client.ConnectAsync(Configuration.Host, Configuration.Port, + using var client = new SmtpClient(); + await client.ConnectAsync(Configuration.Host, Configuration.Port, Configuration.Ssl ? SecureSocketOptions.SslOnConnect : SecureSocketOptions.None); - if (!string.IsNullOrWhiteSpace(Configuration.Username)) { + if (!string.IsNullOrWhiteSpace(Configuration.Username)) { await client.AuthenticateAsync(Configuration.Username, Configuration.Password); } @@ -57,22 +63,40 @@ public class SmtpEmailSender(ILogger logger, IOptions"), + new Header(HeaderId.ListUnsubscribePost, "One-Click")); + } } public class EmailNotSendException(string message, Exception exception) : ApplicationException(message, exception); \ No newline at end of file