SendGrid integration

master
Tudor Stanciu 2023-01-20 19:23:28 +02:00
parent 924824437f
commit 991cb525f0
12 changed files with 176 additions and 10 deletions

View File

@ -28,7 +28,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Correo.NetSmtpClient", "src
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Correo.Abstractions", "src\Correo.Abstractions\Correo.Abstractions.csproj", "{ED8048DC-8509-4085-97F0-F9D9A59A689A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Correo.MailKit", "src\Correo.MailKit\Correo.MailKit.csproj", "{325B77E1-D752-4578-8BF7-793905C38DCD}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Correo.MailKit", "src\Correo.MailKit\Correo.MailKit.csproj", "{325B77E1-D752-4578-8BF7-793905C38DCD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Correo.SendGrid", "src\Correo.SendGrid\Correo.SendGrid.csproj", "{1457426A-CD47-4201-BE3C-A40B38FCB1FA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -64,6 +66,10 @@ Global
{325B77E1-D752-4578-8BF7-793905C38DCD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{325B77E1-D752-4578-8BF7-793905C38DCD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{325B77E1-D752-4578-8BF7-793905C38DCD}.Release|Any CPU.Build.0 = Release|Any CPU
{1457426A-CD47-4201-BE3C-A40B38FCB1FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -76,6 +82,7 @@ Global
{76793477-8219-4FFB-AA08-3F2224EC4968} = {245E2FBE-DFDF-40B4-94B7-5DDA216E58AD}
{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}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {86FCF989-26FC-41E9-8A23-9485606D619D}

View File

@ -9,5 +9,6 @@
<MediatRPackageVersion>9.0.0</MediatRPackageVersion>
<NBBPackageVersion>6.0.30</NBBPackageVersion>
<MailKitPackageVersion>3.4.3</MailKitPackageVersion>
<SendGridPackageVersion>9.28.1</SendGridPackageVersion>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="SendGrid" Version="$(SendGridPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Correo.Abstractions\Correo.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,16 @@
using Correo.Abstractions;
using Correo.SendGrid.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Correo.SendGrid
{
public static class DependencyInjectionExtensions
{
public static void AddSendGridService(this IServiceCollection services, IConfigurationSection configuration)
{
services.Configure<SendGridOptions>(configuration);
services.AddTransient<IMailer, SendGridService>();
}
}
}

View File

@ -0,0 +1,40 @@
using Correo.Abstractions;
using SendGrid.Helpers.Mail;
using System.Collections.Generic;
using System.Linq;
namespace Correo.SendGrid.Extensions
{
internal static class ModelExtensions
{
private static EmailAddress ToEmailAddress(this EmailMessage.MailAddress address)
=> new EmailAddress(address.Address, address.DisplayName);
private static List<EmailAddress> ToEmailAddressList(this IEnumerable<EmailMessage.MailAddress> addresses)
=> addresses.Select(z => new EmailAddress(z.Address, z.DisplayName)).ToList();
public static SendGridMessage ToSendGridMessage(this EmailMessage message)
{
var subject = message.Subject;
var plainContent = !message.IsBodyHtml ? message.Body : null;
var htmlContent = message.IsBodyHtml ? message.Body : null;
var from = message.From.ToEmailAddress();
var to = message.To.First().ToEmailAddress();
var mailMessage = MailHelper.CreateSingleEmail(from, to, subject, plainContent, htmlContent);
if (message.To.Count() > 1)
{
var tos = message.To.Skip(1).Select(z => new EmailAddress(z.Address, z.DisplayName)).ToList();
mailMessage.AddTos(tos);
}
if (message.Cc != null && message.Cc.Any())
mailMessage.AddCcs(message.Cc.ToEmailAddressList());
if (message.Bcc != null && message.Bcc.Any())
mailMessage.AddBccs(message.Bcc.ToEmailAddressList());
return mailMessage;
}
}
}

View File

@ -0,0 +1,9 @@
namespace Correo.SendGrid.Models
{
internal record Error
{
public string Message { get; init; }
public string Field { get; init; }
public string Help { get; init; }
}
}

View File

@ -0,0 +1,7 @@
namespace Correo.SendGrid.Models
{
internal record SendGridOptions
{
public string ApiKey { get; init; }
}
}

View File

@ -0,0 +1,61 @@
using Correo.Abstractions;
using Correo.Abstractions.Extensions;
using Correo.SendGrid.Extensions;
using Correo.SendGrid.Models;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using SendGrid;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Correo.SendGrid
{
internal class SendGridService : IMailer
{
private readonly IOptions<SendGridOptions> _optionsAccessor;
private readonly SendGridClient _client;
private readonly ILogger<SendGridService> _logger;
public SendGridService(IOptions<SendGridOptions> optionsAccessor, ILogger<SendGridService> logger)
{
_optionsAccessor = optionsAccessor;
_logger = logger;
_client = new SendGridClient(_optionsAccessor.Value.ApiKey);
}
public void SendEmail(EmailMessage message)
{
var mailMessage = message.ToSendGridMessage();
_client.SendEmailAsync(mailMessage).GetAwaiter().GetResult();
Log(message);
}
public async Task SendEmailAsync(EmailMessage message, CancellationToken token = default)
{
var mailMessage = message.ToSendGridMessage();
var response = await _client.SendEmailAsync(mailMessage, token);
if (response.IsSuccessStatusCode)
Log(message);
else
{
var body = await response.DeserializeResponseBodyAsync();
var found = body.TryGetValue("errors", out var jsonData);
if (found)
{
string errorsString = Convert.ToString(jsonData);
var errors = JsonConvert.DeserializeObject<Error[]>(errorsString);
var errorMessage = string.Join("; ", errors.Select(z => z.Message));
throw new Exception(errorMessage);
}
throw new Exception(response.StatusCode.ToString());
}
}
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()};");
}
}

View File

@ -18,6 +18,7 @@
<ProjectReference Include="..\Correo.Application\Correo.Application.csproj" />
<ProjectReference Include="..\Correo.MailKit\Correo.MailKit.csproj" />
<ProjectReference Include="..\Correo.NetSmtpClient\Correo.NetSmtpClient.csproj" />
<ProjectReference Include="..\Correo.SendGrid\Correo.SendGrid.csproj" />
</ItemGroup>
</Project>

View File

@ -1,5 +1,6 @@
using Correo.MailKit;
using Correo.NetSmtpClient;
using Correo.SendGrid;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
@ -8,22 +9,24 @@ namespace Correo.Extensions
{
public static class SmtpExtensions
{
public static void AddSmtpClient(this IServiceCollection services, IConfiguration configuration)
public static void AddSmtpService(this IServiceCollection services, IConfiguration configuration)
{
var configSection = configuration.GetSection("SmtpClient");
var mediator = configSection.GetValue("Mediator", ".NET");
var mediator = configuration.GetValue("SmtpMediator", ".NET");
if (mediator.Equals(".NET", StringComparison.InvariantCultureIgnoreCase))
{
services.AddNetSmtpClient(configSection);
services.AddNetSmtpClient(configuration.GetSection("SmtpClient"));
}
else if (mediator.Equals("MailKit", StringComparison.InvariantCultureIgnoreCase))
{
services.AddMailKitSmtpClient(configSection);
services.AddMailKitSmtpClient(configuration.GetSection("SmtpClient"));
}
else if (mediator.Equals("SendGrid", StringComparison.InvariantCultureIgnoreCase))
{
services.AddSendGridService(configuration.GetSection("SmtpService"));
}
else
{
throw new NotImplementedException($"SmtpClient:Mediator={mediator} is not implemented.");
throw new NotImplementedException($"SmtpMediator '{mediator}' is not implemented.");
}
}
}

View File

@ -30,7 +30,7 @@ namespace Correo.Extensions
services.AddMessaging(configuration);
// Add SMTP client
services.AddSmtpClient(configuration);
services.AddSmtpService(configuration);
// Application services
services.AddApplicationServices(configuration);

View File

@ -29,8 +29,8 @@
"Address": "noreply@homelab.com",
"Name": "HomeLab"
},
"SmtpMediator": ".NET", //.NET, MailKit, SendGrid
"SmtpClient": {
"Mediator": ".NET", //.NET, MailKit
"Server": "smtp.gmail.com",
"Port": "587",
"UseAuthentication": true,
@ -41,5 +41,8 @@
},
"EnableSsl": true,
"TrustServer": false
},
"SmtpService": {
"ApiKey": "xxxxxxxxxxx"
}
}