Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic AWS SES Implementation #77

Merged
merged 5 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
<PackageVersion Include="AWSSDK.SimpleEmailV2" Version="3.7.405" />
<PackageVersion Include="Azure.Communication.Email" Version="1.0.1" />
<PackageVersion Include="Azure.Identity" Version="1.12.1" />
<PackageVersion Include="Ensure.That" Version="10.1.0" />
Expand Down
6 changes: 6 additions & 0 deletions OneBeyond.Studio.EmailProviders.sln
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OneBeyond.Studio.EmailProvi
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OneBeyond.Studio.EmailProviders.SendGrid.Tests", "src\OneBeyond.Studio.EmailProviders.SendGrid.Tests\OneBeyond.Studio.EmailProviders.SendGrid.Tests.csproj", "{2FB1D7DD-6A61-4377-8C5F-F7FE07D4C729}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OneBeyond.Studio.EmailProviders.AwsSes", "src\OneBeyond.Studio.EmailProviders.AwsSes\OneBeyond.Studio.EmailProviders.AwsSes.csproj", "{8C05577B-5225-4C86-9552-B00E3EA88AAD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -77,6 +79,10 @@ Global
{2FB1D7DD-6A61-4377-8C5F-F7FE07D4C729}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2FB1D7DD-6A61-4377-8C5F-F7FE07D4C729}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2FB1D7DD-6A61-4377-8C5F-F7FE07D4C729}.Release|Any CPU.Build.0 = Release|Any CPU
{8C05577B-5225-4C86-9552-B00E3EA88AAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C05577B-5225-4C86-9552-B00E3EA88AAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C05577B-5225-4C86-9552-B00E3EA88AAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C05577B-5225-4C86-9552-B00E3EA88AAD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

using EnsureThat;
using Microsoft.Extensions.DependencyInjection;
using OneBeyond.Studio.EmailProviders.Domain;
using OneBeyond.Studio.EmailProviders.AwsSes.Options;

namespace OneBeyond.Studio.EmailProviders.AwsSes.DependencyInjection;

/// <summary>
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Configures email sending capabilities on DI container.
/// </summary>
/// <param name="this"></param>
/// <param name="emailSenderOptions">Email sending configuration options</param>
/// <returns></returns>
public static IServiceCollection AddEmailSender(this IServiceCollection @this, EmailSenderOptions emailSenderOptions)
{
EnsureArg.IsNotNull(@this, nameof(@this));
EnsureArg.IsNotNull(emailSenderOptions, nameof(emailSenderOptions));

@this.AddSingleton<IEmailSender>(
(serviceProvider) =>
{
return new EmailSender(
emailSenderOptions.FromEmailAddress,

Check warning on line 28 in src/OneBeyond.Studio.EmailProviders.AwsSes/DependencyInjection/ServiceCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / Build & Run Tests

Possible null reference argument for parameter 'defaultFromAddress' in 'EmailSender.EmailSender(string defaultFromAddress, string? enforcedToEmailAddress)'.

Check warning on line 28 in src/OneBeyond.Studio.EmailProviders.AwsSes/DependencyInjection/ServiceCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / Build & Run Tests

Possible null reference argument for parameter 'defaultFromAddress' in 'EmailSender.EmailSender(string defaultFromAddress, string? enforcedToEmailAddress)'.
emailSenderOptions.UseEnforcedToEmailAddress
? emailSenderOptions.EnforcedToEmailAddress
: default
);
});
return @this;
}
}
106 changes: 106 additions & 0 deletions src/OneBeyond.Studio.EmailProviders.AwsSes/EmailSender.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using Amazon.Runtime.Internal;
using Amazon.SimpleEmailV2;
using Amazon.SimpleEmailV2.Model;
using EnsureThat;
using OneBeyond.Studio.EmailProviders.Domain;
using OneBeyond.Studio.EmailProviders.Domain.Exceptions;
using System.Net.Mail;

namespace OneBeyond.Studio.EmailProviders.AwsSes;

/// <summary>
/// Email Sender for Amazon SES (Simple Email Service). This utilises SES V2
/// API. Currently we only support SSO/IAM based authentication. This could be
/// extended in future to support AWS Access Key/Secret authentication
/// </summary>
internal sealed class EmailSender : IEmailSender
{
private readonly AmazonSimpleEmailServiceV2Client _emailClient;
private readonly string _defaultFromAddress;
private readonly string? _enforcedToEmailAddress;

public EmailSender(
string defaultFromAddress,
string? enforcedToEmailAddress)
{
// Picks up profile from appsettings
_emailClient = new AmazonSimpleEmailServiceV2Client();
_defaultFromAddress = defaultFromAddress;
_enforcedToEmailAddress = enforcedToEmailAddress;
}

public async Task<string?> SendEmailAsync(MailMessage mailMessage, CancellationToken cancellationToken = default)
{
EnsureArg.IsNotNull(mailMessage, nameof(mailMessage));

SendEmailRequest ser = new SendEmailRequest();

ser.FromEmailAddress = mailMessage.From?.Address ?? _defaultFromAddress;

var toAddressesList = string.IsNullOrEmpty(_enforcedToEmailAddress)
? mailMessage.To.Select(x => x.Address).ToList()
: _enforcedToEmailAddress.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList();


ser.Destination = new Destination()
{
ToAddresses = toAddressesList
};

if (string.IsNullOrWhiteSpace(_enforcedToEmailAddress))
{
ser.Destination.CcAddresses = mailMessage.CC.Select(x => x.Address).ToList();
ser.Destination.BccAddresses = mailMessage.Bcc.Select(x => x.Address).ToList();
}

var body = new Body();
if (mailMessage.IsBodyHtml)
{
body.Html = new Content { Data = mailMessage.Body };
}
else
{
body.Text = new Content { Data = mailMessage.Body };
}

ser.Content = new EmailContent
{
Simple = new Message
{
Subject = new Content { Data = mailMessage.Subject },
Body = body
}
};

try
{
var response = await _emailClient.SendEmailAsync(ser);
// SES message id
return response.MessageId;
}
catch (AccountSuspendedException ex)
{
throw new EmailSenderException("The account's ability to send email has been permanently restricted.", ex);
}
catch (MailFromDomainNotVerifiedException ex)
{
throw new EmailSenderException("The sending domain is not verified.", ex);
}
catch (MessageRejectedException ex)
{
throw new EmailSenderException("The message content is invalid.", ex);
}
catch (SendingPausedException ex)
{
throw new EmailSenderException("The account's ability to send email is currently paused.", ex);
}
catch (TooManyRequestsException ex)
{
throw new EmailSenderException("Too many requests were made. Please try again later.", ex);
}
catch (Exception ex)
{
throw new EmailSenderException($"An error occurred while sending the email", ex);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AWSSDK.SimpleEmailV2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\OneBeyond.Studio.EmailProviders.Domain\OneBeyond.Studio.EmailProviders.Domain.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace OneBeyond.Studio.EmailProviders.AwsSes.Options;
/// <summary>
/// Options for email sending
/// </summary>
public sealed record EmailSenderOptions : Domain.Options.EmailSenderOptions
{

}
Loading