Send Emails Using a Dedicated Custom Email Sending Service
Create an email sending microservice (really just a template) to speed up sending emails in your new projects.
Start by creating a new email microservice that exposes a single endpoint api/Email/Send
and accepts the following payload:
{
"to": "sample.user@example.com",
"subject": "Save Time on Future Projects",
"body": "Prepare to save some time.",
"isBodyHtml": false
}
Now, whenever you need to add email sending to a project you're working on, simply deploy this at a subdomain like emailservice.example.com
Create a new EmailService
folder in your Infrastructure layer and add EmailServiceOptions.cs
file that has a single BaseUrl
property. In hindsight, I could've just used the primitive string type without the overhead of the Options pattern.
Then create the EmailRequest.cs
file (you can use a record
for immutability):
public class EmailRequest
{
public required string To { get; set; }
public required string Subject { get; set; }
public required string Body { get; set; }
public bool IsHtmlBody { get; set; }
}
Create the IEmailSender
interface
public interface IEmailSender
{
Task SendEmailAsync(EmailRequest emailRequest);
}
Implement it
public class EmailSender : IEmailSender
{
private readonly HttpClient _httpClient;
private readonly ILogger<EmailSender> _logger;
public EmailSender(IHttpClientFactory httpClientFactory, ILogger<EmailSender> logger)
{
_httpClient = httpClientFactory.CreateClient("EmailService");
_logger = logger;
}
public async Task SendEmailAsync(EmailRequest emailRequest)
{
_logger.LogInformation("Sending email to {To}", emailRequest.To);
var request = new HttpRequestMessage
{
Method = HttpMethod.Post,
Content = JsonContent.Create(emailRequest),
};
var response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
_logger.LogError("Failed to send email to {To}: {StatusCode}", emailRequest.To, response.StatusCode);
var errorContent = await response.Content.ReadAsStringAsync();
_logger.LogError("Error content: {ErrorContent}", errorContent);
throw new InvalidOperationException(
$"Failed to send email. Status code: {response.StatusCode}, Error: {errorContent}");
}
_logger.LogInformation("Email sent successfully to {To}", emailRequest.To);
}
}
And register the service in your service collection (i.e dependency injection container)
var emailServiceConfig = configuration
.GetSection("EmailService")
.Get<EmailServiceOptions>()
?? throw new InvalidOperationException("EmailService configuration is missing.");
services.AddHttpClient("EmailService", client =>
{
client.BaseAddress = new Uri(emailServiceConfig.BaseUrl);
client.DefaultRequestHeaders.Add("Accept", "application/json");
});
services.Configure<EmailServiceOptions>(configuration.GetSection("EmailService"));
services.AddHttpContextAccessor();
services.AddScoped<IEmailSender, EmailSender>();
Add the email service configurations to your appsettings.json
file (or whatever method you see fit for your use case)
"EmailService": {
"BaseUrl": "https://emailservice.example.com/api/Email/send"
}
And that is it. You can now inject the email service interface in your services and start sending emails in minutes.