dotnet-email-service-aws-ses
Implements email service using AWS SES for .NET APIs. Designed for applications with HTML template support, placeholder replacement, and Result pattern error handling.
git clone --depth 1 https://github.com/ronnythedev/dotnet-clean-architecture-skills /tmp/dotnet-email-service-aws-ses && cp -r /tmp/dotnet-email-service-aws-ses/skills/16.2-dotnet-email-service-aws-ses ~/.claude/skills/dotnet-email-service-aws-sesSKILL.md
# AWS SES Email Service
## Overview
This skill implements email delivery via AWS SES for APIs:
- **AWS SES Integration** - Production-ready email delivery
- **HTML Templates** - File-based templates with placeholder replacement
- **Result Pattern** - No exceptions, returns `Result<T>` for error handling
- **Enable/Disable Toggle** - Development mode without actual sending
---
## Quick Reference
| Component | Purpose | Location |
|-----------|---------|----------|
| `IEmailService` | Email abstraction interface | Application/Abstractions/Email |
| `AwsSesEmailService` | AWS SES implementation | Infrastructure/Email |
| `EmailOptions` | AWS SES configuration | Infrastructure/Email |
| `EmailErrors` | Error definitions | Application/Abstractions/Email |
---
## Email Structure
```
/Application/Abstractions/
├── Email/
│ ├── IEmailService.cs
│ └── EmailErrors.cs
/Infrastructure/
├── Email/
│ ├── EmailOptions.cs
│ └── AwsSesEmailService.cs
/Api/
├── EmailTemplates/
│ ├── appointment-reminder.html
│ ├── appointment-reminder-es.html
│ ├── test-results-ready.html
│ ├── prescription-ready.html
│ ├── welcome.html
│ └── password-reset.html
```
---
## Template: Email Service Interface
```csharp
// src/{name}.application/Abstractions/Email/IEmailService.cs
using {name}.domain.abstractions;
namespace {name}.application.Abstractions.Email;
/// <summary>
/// Service for sending emails via AWS SES
/// Returns Result pattern for error handling (no exceptions)
/// </summary>
public interface IEmailService
{
/// <summary>
/// Send an email using a template file with placeholder replacements
/// </summary>
/// <param name="toEmail">Recipient email address</param>
/// <param name="subject">Email subject</param>
/// <param name="templateName">Name of the template file (without extension)</param>
/// <param name="placeholders">Dictionary of placeholder keys and replacement values</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Result indicating success or failure</returns>
Task<r> SendTemplatedEmailAsync(
string toEmail,
string subject,
string templateName,
Dictionary<string, string> placeholders,
CancellationToken cancellationToken = default);
/// <summary>
/// Send an email with raw HTML content
/// </summary>
/// <param name="toEmail">Recipient email address</param>
/// <param name="subject">Email subject</param>
/// <param name="htmlBody">HTML content of the email</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Result indicating success or failure</returns>
Task<r> SendHtmlEmailAsync(
string toEmail,
string subject,
string htmlBody,
CancellationToken cancellationToken = default);
}
```
---
## Template: Email Errors
```csharp
// src/{name}.application/Abstractions/Email/EmailErrors.cs
using {name}.domain.abstractions;
namespace {name}.application.Abstractions.Email;
public static class EmailErrors
{
public static readonly Error SendFailed = new(
"Email.SendFailed",
"Failed to send email. Please try again later.");
public static readonly Error TemplateNotFound = new(
"Email.TemplateNotFound",
"Email template not found.");
public static readonly Error InvalidRecipient = new(
"Email.InvalidRecipient",
"Invalid recipient email address.");
public static readonly Error EmailDisabled = new(
"Email.Disabled",
"Email sending is currently disabled.");
}
```
---
## Template: Email Options
```csharp
// src/{name}.infrastructure/Email/EmailOptions.cs
namespace {name}.infrastructure.Email;
public sealed class EmailOptions
{
public const string SectionName = "Email";
/// <summary>
/// AWS region for SES (e.g., "us-east-1")
/// </summary>
public string AwsRegion { get; init; } = "us-east-1";
/// <summary>
/// AWS access key ID (optional - use IAM role in production)
/// </summary>
public string? AwsAccessKeyId { get; init; }
/// <summary>
/// AWS secret access key (optional - use IAM role in production)
/// </summary>
public string? AwsSecretAccessKey { get; init; }
/// <summary>
/// Email address to send from
/// </summary>
public string FromAddress { get; init; } = string.Empty;
/// <summary>
/// Display name for the sender (e.g., "Support Team")
/// </summary>
public string FromName { get; init; } = string.Empty;
/// <summary>
/// Whether email sending is enabled (disable for development)
/// </summary>
public bool Enabled { get; init; } = false;
/// <summary>
/// Path to email templates directory (relative to app base)
/// </summary>
public string TemplatesPath { get; init; } = "EmailTemplates";
}
```
### appsettings.json
```json
{
"Email": {
"AwsRegion": "us-east-1",
"AwsAccessKeyId": "",
"AwsSecretAccessKey": "",
"FromAddress": "noreply@healthcare.example.com",
"FromName": "Supoort Team",
"Enabled": true,
"TemplatesPath": "EmailTemplates"
}
}
```
---
## Template: AWS SES Email Service Implementation
```csharp
// src/{name}.infrastructure/Email/AwsSesEmailService.cs
using Amazon;
using Amazon.SimpleEmailV2;
using Amazon.SimpleEmailV2.Model;
using {name}.application.Abstractions.Email;
using {name}.domain.abstractions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace {name}.infrastructure.Email;
internal sealed class AwsSesEmailService : IEmailService
{
private readonly EmailOptions _options;
private readonly ILogger<AwsSesEmailService> _logger;
private readonly IAmazonSimpleEmailServiceV2 _sesClient;
private readonly string _templatesPath;
public AwsSesEmailService(
IOptions<EmailOptions> options,
ILogger<AwsSesEmailService> logger)
{
_oScaffolds a complete .NET solution following Clean Architecture principles with proper layer separation (API, Application, Domain, Infrastructure). Creates project structure, dependency injection setup, and cross-cutting concerns configuration.
Generates CQRS Commands with Handlers, Validators, and Request DTOs following Clean Architecture patterns. Commands represent actions that modify state and return Result types for proper error handling.
Generates CQRS Queries with Handlers and Response DTOs for read operations. Uses Dapper for optimized read queries, bypassing the domain model for better performance.
Generates Domain Entities following DDD principles with factory methods, private setters, domain events, and proper encapsulation. Supports aggregate roots, child entities, and value objects.
Generates Repository interfaces and implementations following the Repository pattern. Provides data access abstraction for aggregate roots with EF Core implementations.
Generates Entity Framework Core configurations using Fluent API. Maps domain entities to database tables with proper relationships, constraints, and conventions.
Generates RESTful API Controllers with proper routing, versioning, authorization, and MediatR integration. Follows REST conventions and Clean Architecture patterns.
Generates Minimal API endpoints following Microsoft's recommended approach. Creates fast, testable HTTP APIs with minimal code using MapGet/MapPost/MapPut/MapDelete. Preferred over controller-based APIs for new projects.