From 35124b515db69d67e89fe348bb8c83ac0199cf1e Mon Sep 17 00:00:00 2001 From: Tudor Stanciu Date: Sat, 21 Jan 2023 04:05:14 +0200 Subject: [PATCH] Mailgun integration --- Correo.sln | 7 ++ dependencies.props | 1 + src/Correo.Mailgun/Correo.Mailgun.csproj | 18 +++++ .../DependencyInjectionExtensions.cs | 16 ++++ .../Extensions/ModelExtensions.cs | 20 +++++ src/Correo.Mailgun/MailgunService.cs | 79 +++++++++++++++++++ src/Correo.Mailgun/Models/MailgunOptions.cs | 8 ++ src/Correo.Mailgun/Models/MailgunResponse.cs | 8 ++ src/Correo.Mailgun/readme.txt | 1 + src/Correo.SendGrid/SendGridService.cs | 4 +- src/Correo/Correo.csproj | 1 + src/Correo/Extensions/SmtpExtensions.cs | 9 ++- src/Correo/appsettings.json | 8 +- 13 files changed, 173 insertions(+), 7 deletions(-) create mode 100644 src/Correo.Mailgun/Correo.Mailgun.csproj create mode 100644 src/Correo.Mailgun/DependencyInjectionExtensions.cs create mode 100644 src/Correo.Mailgun/Extensions/ModelExtensions.cs create mode 100644 src/Correo.Mailgun/MailgunService.cs create mode 100644 src/Correo.Mailgun/Models/MailgunOptions.cs create mode 100644 src/Correo.Mailgun/Models/MailgunResponse.cs create mode 100644 src/Correo.Mailgun/readme.txt diff --git a/Correo.sln b/Correo.sln index 6c2f4c1..bf05160 100644 --- a/Correo.sln +++ b/Correo.sln @@ -32,6 +32,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Correo.MailKit", "src\Corre EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Correo.SendGrid", "src\Correo.SendGrid\Correo.SendGrid.csproj", "{1457426A-CD47-4201-BE3C-A40B38FCB1FA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Correo.Mailgun", "src\Correo.Mailgun\Correo.Mailgun.csproj", "{78DC392D-B24A-43CA-B632-6124CD2C35B1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -70,6 +72,10 @@ Global {1457426A-CD47-4201-BE3C-A40B38FCB1FA}.Debug|Any CPU.Build.0 = Debug|Any CPU {1457426A-CD47-4201-BE3C-A40B38FCB1FA}.Release|Any CPU.ActiveCfg = Release|Any CPU {1457426A-CD47-4201-BE3C-A40B38FCB1FA}.Release|Any CPU.Build.0 = Release|Any CPU + {78DC392D-B24A-43CA-B632-6124CD2C35B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78DC392D-B24A-43CA-B632-6124CD2C35B1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78DC392D-B24A-43CA-B632-6124CD2C35B1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78DC392D-B24A-43CA-B632-6124CD2C35B1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -83,6 +89,7 @@ Global {ED8048DC-8509-4085-97F0-F9D9A59A689A} = {245E2FBE-DFDF-40B4-94B7-5DDA216E58AD} {325B77E1-D752-4578-8BF7-793905C38DCD} = {245E2FBE-DFDF-40B4-94B7-5DDA216E58AD} {1457426A-CD47-4201-BE3C-A40B38FCB1FA} = {245E2FBE-DFDF-40B4-94B7-5DDA216E58AD} + {78DC392D-B24A-43CA-B632-6124CD2C35B1} = {245E2FBE-DFDF-40B4-94B7-5DDA216E58AD} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {86FCF989-26FC-41E9-8A23-9485606D619D} diff --git a/dependencies.props b/dependencies.props index 2480ea9..7180720 100644 --- a/dependencies.props +++ b/dependencies.props @@ -10,5 +10,6 @@ 6.0.30 3.4.3 9.28.1 + 108.0.3 \ No newline at end of file diff --git a/src/Correo.Mailgun/Correo.Mailgun.csproj b/src/Correo.Mailgun/Correo.Mailgun.csproj new file mode 100644 index 0000000..b496ce3 --- /dev/null +++ b/src/Correo.Mailgun/Correo.Mailgun.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + + + + + + + + + + + + + + diff --git a/src/Correo.Mailgun/DependencyInjectionExtensions.cs b/src/Correo.Mailgun/DependencyInjectionExtensions.cs new file mode 100644 index 0000000..81d34a5 --- /dev/null +++ b/src/Correo.Mailgun/DependencyInjectionExtensions.cs @@ -0,0 +1,16 @@ +using Correo.Abstractions; +using Correo.Mailgun.Models; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Correo.Mailgun +{ + public static class DependencyInjectionExtensions + { + public static void AddMailgunService(this IServiceCollection services, IConfigurationSection configuration) + { + services.Configure(configuration); + services.AddTransient(); + } + } +} \ No newline at end of file diff --git a/src/Correo.Mailgun/Extensions/ModelExtensions.cs b/src/Correo.Mailgun/Extensions/ModelExtensions.cs new file mode 100644 index 0000000..ca29b08 --- /dev/null +++ b/src/Correo.Mailgun/Extensions/ModelExtensions.cs @@ -0,0 +1,20 @@ +using Correo.Abstractions; +using System.Collections.Generic; +using System.Linq; + +namespace Correo.Mailgun.Extensions +{ + internal static class ModelExtensions + { + public static string ToMailgunAddress(this EmailMessage.MailAddress address) + => $"{address.DisplayName} <{address.Address}>"; + + public static string ToMailgunAddresses(this IEnumerable addresses) + { + if (addresses == null || !addresses.Any()) + return null; + + return string.Join(',', addresses.Select(z => z.ToMailgunAddress())); + } + } +} diff --git a/src/Correo.Mailgun/MailgunService.cs b/src/Correo.Mailgun/MailgunService.cs new file mode 100644 index 0000000..4534780 --- /dev/null +++ b/src/Correo.Mailgun/MailgunService.cs @@ -0,0 +1,79 @@ +using Correo.Abstractions; +using Correo.Abstractions.Extensions; +using Correo.Mailgun.Extensions; +using Correo.Mailgun.Models; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using RestSharp; +using RestSharp.Authenticators; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Correo.Mailgun +{ + internal class MailgunService : IMailer, IDisposable + { + private readonly IOptions _optionsAccessor; + private readonly RestClient _client; + private readonly ILogger _logger; + + public MailgunService(IOptions optionsAccessor, ILogger logger) + { + _optionsAccessor = optionsAccessor; + _logger = logger; + _client = new RestClient("https://api.mailgun.net/v3") + { + Authenticator = new HttpBasicAuthenticator("api", _optionsAccessor.Value.ApiKey) + }; + } + + public void Dispose() + { + _client.Dispose(); + GC.SuppressFinalize(this); + } + + public void SendEmail(EmailMessage message) + { + SendEmailAsync(message).GetAwaiter().GetResult(); + } + + public async Task SendEmailAsync(EmailMessage message, CancellationToken token = default) + { + var request = new RestRequest(); + request.AddParameter("domain", _optionsAccessor.Value.Domain, ParameterType.UrlSegment); + request.Resource = "{domain}/messages"; + + request.AddParameter("subject", message.Subject); + var contentKey = message.IsBodyHtml ? "html" : "text"; + request.AddParameter(contentKey, message.Body); + + request.AddParameter("from", message.From.ToMailgunAddress()); + request.AddParameter("to", message.To.ToMailgunAddresses()); + + var cc = message.Cc.ToMailgunAddresses(); + if (cc != null) + request.AddParameter("cc", cc); + + var bcc = message.Bcc.ToMailgunAddresses(); + if (bcc != null) + request.AddParameter("bcc", bcc); + + request.Method = Method.Post; + var response = await _client.ExecuteAsync(request, token); + if (response.IsSuccessful) + { + _logger.LogInformation($"Mailgun response: {response.Data.Message} {response.Data.Id}"); + Log(message); + } + else + { + throw new Exception($"Mailgun error: {response.Data.Message}"); + } + } + + private void Log(EmailMessage message) + => _logger.LogInformation($"Email sent: Subject: {message.Subject}; From: {message.From.Address}; To: {message.To.Log()}; Cc: {message.Cc.Log()}; Bcc: {message.Bcc.Log()};"); + } +} diff --git a/src/Correo.Mailgun/Models/MailgunOptions.cs b/src/Correo.Mailgun/Models/MailgunOptions.cs new file mode 100644 index 0000000..0ba1ccf --- /dev/null +++ b/src/Correo.Mailgun/Models/MailgunOptions.cs @@ -0,0 +1,8 @@ +namespace Correo.Mailgun.Models +{ + internal record MailgunOptions + { + public string ApiKey { get; init; } + public string Domain { get; init; } + } +} diff --git a/src/Correo.Mailgun/Models/MailgunResponse.cs b/src/Correo.Mailgun/Models/MailgunResponse.cs new file mode 100644 index 0000000..6a4dbba --- /dev/null +++ b/src/Correo.Mailgun/Models/MailgunResponse.cs @@ -0,0 +1,8 @@ +namespace Correo.Mailgun.Models +{ + public record MailgunResponse + { + public string Id { get; init; } + public string Message { get; init; } + } +} diff --git a/src/Correo.Mailgun/readme.txt b/src/Correo.Mailgun/readme.txt new file mode 100644 index 0000000..661b241 --- /dev/null +++ b/src/Correo.Mailgun/readme.txt @@ -0,0 +1 @@ +Docs: https://documentation.mailgun.com/en/latest/api-sending.html#sending \ No newline at end of file diff --git a/src/Correo.SendGrid/SendGridService.cs b/src/Correo.SendGrid/SendGridService.cs index e111fdb..2c67d9d 100644 --- a/src/Correo.SendGrid/SendGridService.cs +++ b/src/Correo.SendGrid/SendGridService.cs @@ -28,9 +28,7 @@ namespace Correo.SendGrid public void SendEmail(EmailMessage message) { - var mailMessage = message.ToSendGridMessage(); - _client.SendEmailAsync(mailMessage).GetAwaiter().GetResult(); - Log(message); + SendEmailAsync(message).GetAwaiter().GetResult(); } public async Task SendEmailAsync(EmailMessage message, CancellationToken token = default) diff --git a/src/Correo/Correo.csproj b/src/Correo/Correo.csproj index d51adaa..aae0fe1 100644 --- a/src/Correo/Correo.csproj +++ b/src/Correo/Correo.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Correo/Extensions/SmtpExtensions.cs b/src/Correo/Extensions/SmtpExtensions.cs index cee12ee..d1eae84 100644 --- a/src/Correo/Extensions/SmtpExtensions.cs +++ b/src/Correo/Extensions/SmtpExtensions.cs @@ -1,4 +1,5 @@ -using Correo.MailKit; +using Correo.Mailgun; +using Correo.MailKit; using Correo.NetSmtpClient; using Correo.SendGrid; using Microsoft.Extensions.Configuration; @@ -22,7 +23,11 @@ namespace Correo.Extensions } else if (mediator.Equals("SendGrid", StringComparison.InvariantCultureIgnoreCase)) { - services.AddSendGridService(configuration.GetSection("SmtpService")); + services.AddSendGridService(configuration.GetSection("SendGrid")); + } + else if (mediator.Equals("Mailgun", StringComparison.InvariantCultureIgnoreCase)) + { + services.AddMailgunService(configuration.GetSection("Mailgun")); } else { diff --git a/src/Correo/appsettings.json b/src/Correo/appsettings.json index 7341fcb..9955318 100644 --- a/src/Correo/appsettings.json +++ b/src/Correo/appsettings.json @@ -29,7 +29,7 @@ "Address": "noreply@homelab.com", "Name": "HomeLab" }, - "SmtpMediator": ".NET", //.NET, MailKit, SendGrid + "SmtpMediator": ".NET", //.NET, MailKit, SendGrid, Mailgun "SmtpClient": { "Server": "smtp.gmail.com", "Port": "587", @@ -42,7 +42,11 @@ "EnableSsl": true, "TrustServer": false }, - "SmtpService": { + "SendGrid": { "ApiKey": "xxxxxxxxxxx" + }, + "Mailgun": { + "ApiKey": "xxxxxxxxxxx", + "Domain": "xxxxxxxxxxx" } } \ No newline at end of file