diff --git a/Ateleris.NET.Shared/Extensions/EmailServiceExtensions.cs b/Ateleris.NET.Shared/Extensions/EmailServiceExtensions.cs index d787cde..dadb5be 100644 --- a/Ateleris.NET.Shared/Extensions/EmailServiceExtensions.cs +++ b/Ateleris.NET.Shared/Extensions/EmailServiceExtensions.cs @@ -61,6 +61,24 @@ public class StaticEmailTemplateProviderOptions "Your Reset Code", "Please use the following code to reset your password:", ""); + + public EmailTemplate AccountApproved { get; set; } = new( + "Account Approved - {0}", + "Account Approved", + "Great news! Your account has been approved. You can now access the platform.", + "Access Platform"); + + public EmailTemplate AccountApprovedWithConfirmation { get; set; } = new( + "Account Approved - {0}", + "Account Approved - Please Confirm Your Email", + "Great news! Your account has been approved. However, before you can access the platform, you need to confirm your email address. Please click the button below to confirm your email and complete your account setup.", + "Confirm Email & Access Platform"); + + public EmailTemplate AccountCreatedByAdmin { get; set; } = new( + "Account Created - {0}", + "Welcome - Please Confirm Your Email", + "An administrator has created an account for you. Before you can access the platform, you need to confirm your email address. A temporary password has been provided separately by the administrator. You can change your password at any time using the 'Forgot Password' option on the login page.", + "Confirm Email & Access Platform"); } public class StaticEmailTemplateProvider(StaticEmailTemplateProviderOptions options) : IEmailTemplateProvider @@ -84,4 +102,22 @@ public Task GetResetPasswordCodeTemplateAsync(string domain) var template = _options.ResetPasswordCode with { Subject = string.Format(_options.ResetPasswordCode.Subject, domain) }; return Task.FromResult(template); } + + public Task GetAccountApprovedTemplateAsync(string domain) + { + var template = _options.AccountApproved with { Subject = string.Format(_options.AccountApproved.Subject, domain) }; + return Task.FromResult(template); + } + + public Task GetAccountApprovedWithConfirmationTemplateAsync(string domain) + { + var template = _options.AccountApprovedWithConfirmation with { Subject = string.Format(_options.AccountApprovedWithConfirmation.Subject, domain) }; + return Task.FromResult(template); + } + + public Task GetAccountCreatedByAdminTemplateAsync(string domain) + { + var template = _options.AccountCreatedByAdmin with { Subject = string.Format(_options.AccountCreatedByAdmin.Subject, domain) }; + return Task.FromResult(template); + } } diff --git a/Ateleris.NET.Shared/Services/EmailTemplateRenderer.cs b/Ateleris.NET.Shared/Services/EmailTemplateRenderer.cs index 80a1469..1e1d139 100644 --- a/Ateleris.NET.Shared/Services/EmailTemplateRenderer.cs +++ b/Ateleris.NET.Shared/Services/EmailTemplateRenderer.cs @@ -11,7 +11,7 @@ public string RenderStandardEmail(string title, string message, string buttonUrl { var content = new StringBuilder(); content.Append(FormatElement(_options.TitleFormat, title)); - content.Append(FormatElement(_options.ContentFormat, $"

{message}

")); + content.Append(FormatElement(_options.ContentFormat, $"

{ConvertNewlinesToHtml(message)}

")); if (!string.IsNullOrEmpty(buttonUrl) && !string.IsNullOrEmpty(buttonText)) { @@ -27,7 +27,7 @@ public string RenderCodeEmail(string title, string message, string code) { var content = new StringBuilder(); content.Append(FormatElement(_options.TitleFormat, title)); - content.Append(FormatElement(_options.ContentFormat, $"

{message}

")); + content.Append(FormatElement(_options.ContentFormat, $"

{ConvertNewlinesToHtml(message)}

")); content.Append(FormatElement(_options.CodeFormat, code)); content.Append(FormatElement(_options.FooterFormat, DateTime.Now.Year, _options.Domain)); @@ -43,6 +43,11 @@ private static string FormatElement(string format, params object[] args) { return string.Format(format, args); } + + private static string ConvertNewlinesToHtml(string text) + { + return text.Replace("\r\n", "
").Replace("\n", "
").Replace("\r", "
"); + } } public class EmailTemplateOptions diff --git a/Ateleris.NET.Shared/Services/IEmailTemplateProvider.cs b/Ateleris.NET.Shared/Services/IEmailTemplateProvider.cs index 6e2e0cf..8e1ed56 100644 --- a/Ateleris.NET.Shared/Services/IEmailTemplateProvider.cs +++ b/Ateleris.NET.Shared/Services/IEmailTemplateProvider.cs @@ -7,6 +7,9 @@ public interface IEmailTemplateProvider Task GetConfirmAccountTemplateAsync(string domain); Task GetResetPasswordTemplateAsync(string domain); Task GetResetPasswordCodeTemplateAsync(string domain); + Task GetAccountApprovedTemplateAsync(string domain); + Task GetAccountApprovedWithConfirmationTemplateAsync(string domain); + Task GetAccountCreatedByAdminTemplateAsync(string domain); } public record EmailTemplate(string Subject, string Title, string Message, string ButtonText); diff --git a/Ateleris.NET.Shared/Services/IdentityEmailSenderService.cs b/Ateleris.NET.Shared/Services/IdentityEmailSenderService.cs index 1b05c4a..48935f5 100644 --- a/Ateleris.NET.Shared/Services/IdentityEmailSenderService.cs +++ b/Ateleris.NET.Shared/Services/IdentityEmailSenderService.cs @@ -98,6 +98,54 @@ public async Task SendPasswordResetCodeAsync(TUser user, string email, string re var content = new TextPart(MimeKit.Text.TextFormat.Html) { Text = emailContent }; await SendEmailAsync(email, template.Subject, content); } + + public async Task SendAccountApprovedAsync(TUser user, string email, string loginLink) + { + logger.LogTrace("Sending account approval notification to user {user}", user.UserName); + + var template = await templateProvider.GetAccountApprovedTemplateAsync(_options.Domain); + + var emailContent = templateRenderer.RenderStandardEmail( + template.Title, + template.Message, + loginLink, + template.ButtonText); + + var content = new TextPart(MimeKit.Text.TextFormat.Html) { Text = emailContent }; + await SendEmailAsync(email, template.Subject, content); + } + + public async Task SendAccountApprovedWithConfirmationAsync(TUser user, string email, string confirmationLink) + { + logger.LogTrace("Sending account approval notification with email confirmation to user {user}", user.UserName); + + var template = await templateProvider.GetAccountApprovedWithConfirmationTemplateAsync(_options.Domain); + + var emailContent = templateRenderer.RenderStandardEmail( + template.Title, + template.Message, + confirmationLink, + template.ButtonText); + + var content = new TextPart(MimeKit.Text.TextFormat.Html) { Text = emailContent }; + await SendEmailAsync(email, template.Subject, content); + } + + public async Task SendAccountCreatedByAdminAsync(TUser user, string email, string confirmationLink) + { + logger.LogTrace("Sending account created by admin notification to user {user}", user.UserName); + + var template = await templateProvider.GetAccountCreatedByAdminTemplateAsync(_options.Domain); + + var emailContent = templateRenderer.RenderStandardEmail( + template.Title, + template.Message, + confirmationLink, + template.ButtonText); + + var content = new TextPart(MimeKit.Text.TextFormat.Html) { Text = emailContent }; + await SendEmailAsync(email, template.Subject, content); + } } public class EmailOptions