using Microsoft.Extensions.Logging; using System.Net.Mail; using System.Text; using System.Text.RegularExpressions; namespace zero.Mails; public class MailProvider : IMailProvider { protected IMailTemplatesStore Collection { get; set; } protected ILogger Logger { get; set; } protected IZeroContext Zero { get; set; } protected IMailDispatcher MailSender { get; set; } protected IRazorRenderer Renderer { get; set; } protected Regex PlaceholderRegex { get; set; } private Encoding encoding = Encoding.UTF8; public MailProvider(IZeroContext zero, IMailTemplatesStore collection, ILogger logger, IMailDispatcher mailSender, IRazorRenderer renderer) { Zero = zero; Collection = collection; Logger = logger; MailSender = mailSender; Renderer = renderer; PlaceholderRegex = new Regex("{([\\w-_.]+)}", RegexOptions.IgnoreCase); } /// public virtual async Task Create(string mailTemplateKey, Action onCreate = null) where T : Mail, new() { MailTemplate template = await GetMailTemplate(mailTemplateKey); if (template == null) { Logger.LogError("Could not find a mail template with the key {key}", mailTemplateKey); return null; } onCreate?.Invoke(template); return Merge(new T(), template); } /// public virtual async Task Create(string mailTemplateKey, Action onCreate = null) { MailTemplate template = await GetMailTemplate(mailTemplateKey); if (template == null) { Logger.LogError("Could not find a mail template with the key {key}", mailTemplateKey); return null; } onCreate?.Invoke(template); return Merge(new Mail(), template); } /// public virtual async Task Send(Mail message, CancellationToken token = default) { await Send(message, MailSender, token); } /// public virtual async Task Send(Mail message, IMailDispatcher dispatcher, CancellationToken token = default) { if (message.IsDeactivated) { return; } try { await Render(message); dispatcher.Enqueue(message); await dispatcher.Send(token); Logger.LogInformation("Dispatched email (template: {template}) to {recipient}", message.Template?.Alias ?? "none", message.To); } catch (Exception ex) { Logger.LogError(ex, "Failed to send mail message with template @{template}", message.Template.Key); } } protected virtual async Task GetMailTemplate(string key) { return await Collection.GetByKey(key); } /// public virtual async Task Render(Mail message) { message.Subject = TokenReplacement.Apply(message.Subject, message.Placeholders); message.Body = TokenReplacement.Apply(message.Body, message.Placeholders); message.Preheader = TokenReplacement.Apply(message.Preheader, message.Placeholders); if (!message.HasView) { message.IsRendered = true; return message.Body; } string viewPath = message.ViewPath; if (viewPath.IsNullOrEmpty()) { viewPath = Zero.Options.For().BuildViewPath(message); } message.Body = await Renderer.ViewAsync(viewPath, message); message.IsBodyHtml = true; message.IsRendered = true; return message.Body; } protected virtual T Merge(T mail, MailTemplate template) where T : Mail { mail.Template = template; mail.IsDeactivated = !mail.Template.IsActive; // get sender from template or fall back to application mail.From = new MailAddress(template.SenderEmail.Or(Zero.Application.Email), template.SenderName.Or(Zero.Application.FullName), encoding); mail.Sender = mail.From; mail.ReplyToList.Add(mail.From); // cc + bcc (multiple addresses are separated with commas if (!template.Cc.IsNullOrWhiteSpace()) { mail.CC.Add(template.Cc); } if (!template.Bcc.IsNullOrWhiteSpace()) { mail.Bcc.Add(template.Bcc); } // recipient if (!template.RecipientEmail.IsNullOrWhiteSpace()) { mail.To.Add(template.RecipientEmail); } // subject mail.Subject = template.Subject; mail.SubjectEncoding = encoding; // body mail.Body = mail.Template.Body; mail.IsBodyHtml = true; mail.BodyEncoding = encoding; mail.Preheader = mail.Template.Preheader; return mail; } } public interface IMailProvider { /// /// Creates a maling from a template /// Task Create(string mailTemplateKey, Action onCreate = null); /// /// Creates a maling from a template /// Task Create(string mailTemplateKey, Action onCreate = null) where T : Mail, new(); /// /// Renders the message body. /// This is automatically called when sending messages. /// Task Render(Mail message); /// /// Sends a message with the default dispatcher /// Task Send(Mail message, CancellationToken token = default); /// /// Sends a message with the specified dispatcher /// Task Send(Mail message, IMailDispatcher dispatcher, CancellationToken token = default); }