/* * Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: Emails.Transactional * File: SmtpProvider.cs * * SmtpProvider.cs is part of Emails.Transactional which is part of the larger * VNLib collection of libraries and utilities. * * Emails.Transactional is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 2 of the License, * or (at your option) any later version. * * Emails.Transactional is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Emails.Transactional. If not, see http://www.gnu.org/licenses/. */ using System; using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Collections.Generic; using MailKit.Net.Smtp; using MimeKit; using MimeKit.Text; using VNLib.Utils.Extensions; using VNLib.Plugins; using VNLib.Plugins.Extensions.Loading; namespace Emails.Transactional.Mta { [ConfigurationName("smtp")] internal sealed class SmtpProvider : IMailTransferAgent { private readonly Uri ServerAddress; private readonly IAsyncLazy ServerCreds; private readonly TimeSpan Timeout; public SmtpProvider(PluginBase plugin, IConfigScope config) { ServerAddress = config.GetRequiredProperty("server_address", e => new Uri(e.GetString()!)); Timeout = config["timeout_ms"].GetTimeSpan(TimeParseType.Milliseconds); //Get the client id from the config string clientId = config.GetRequiredProperty("username", e => e.GetString()!); //Get the password from the secret store and make it lazy loaded ServerCreds = plugin.GetSecretAsync("smtp_password").ToLazy(r => new NetworkCredential(clientId, r.Result.ToString())); } /// public async Task SendEmailAsync(EmailTransaction transaction, IEmailMessageData template, CancellationToken cancellation = default) { _ = transaction ?? throw new ArgumentNullException(nameof(transaction)); ICredentials creds = await ServerCreds; //Configured a new message using MimeMessage message = new() { Date = DateTime.UtcNow, Subject = transaction.Subject }; //From address is the stored from address message.From.Add(new MailboxAddress(transaction.FromName, transaction.From)); if (transaction.ToAddresses == null) { throw new ArgumentException("The transaction must contain at least one To address"); } //Add to email addresses foreach (KeyValuePair tos in transaction.ToAddresses) { message.To.Add(new MailboxAddress(tos.Value, tos.Key)); } //Add ccs if (transaction.CcAddresses != null) { foreach (KeyValuePair ccs in transaction.CcAddresses) { message.Cc.Add(new MailboxAddress(ccs.Value, ccs.Key)); } } //Add bccs if (transaction.BccAddresses != null) { foreach (KeyValuePair bccs in transaction.BccAddresses) { message.Bcc.Add(new MailboxAddress(bccs.Value, bccs.Key)); } } //Use html format since we expect to be reading html templates using TextPart body = new(TextFormat.Html) { IsAttachment = false }; //Set the body text body.SetText(Encoding.UTF8, template.GetHtml()); //Set message body message.Body = body; //Open a new mail client using SmtpClient client = new(); //Set timeout for senting messages client.Timeout = (int)Timeout.TotalMilliseconds; //Connect to server await client.ConnectAsync(ServerAddress, cancellation); //Authenticate await client.AuthenticateAsync(creds, cancellation); //Send the email transaction.Result = await client.SendAsync(message, cancellation); //Disconnect from the server await client.DisconnectAsync(true, CancellationToken.None); return new MtaResult(true, transaction.Result); } } }