关于 dotnet-email-service-sendgrid
为 .NET 项目接入基于 SendGrid 的抽象邮件服务,支持模板和本地化。提供接口、配置、服务实现及领域事件处理器模板,供应用开发团队使用。
主要功能
- 01应用层 IEmailService 抽象
- 02SendGrid 服务实现
- 03动态邮件模板
- 04多语言模板
- 05通过领域事件触发邮件
适用场景
- 01用户创建后发送欢迎邮件
- 02使用动态内容生成邮件
- 03维护多语言邮件模板
- 04配置 SendGrid 邮件发送
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ ronnythedev/ dotnet-clean-architecture-skills --skill dotnet-email-service-sendgrid安装后在 Claude Code 中输入/dotnet-email-service-sendgrid确认已加载。
详细介绍
邮件服务集成
概述
支持模板的抽象邮件服务:
- 应用中的接口 -
IEmailService - SendGrid 实现 - 可用于生产环境的提供方
- 模板支持 - 使用占位符的动态内容
- 本地化 - 多语言模板
- 领域事件集成 - 在用户执行操作时发送
快速参考
| 组件 | 用途 |
|---|---|
IEmailService | 邮件抽象接口 |
SendGridEmailService | SendGrid 实现 |
EmailOptions | 配置设置 |
EmailTemplate | 模板定义 |
模板:邮件服务接口
// src/{name}.application/Abstractions/Email/IEmailService.cs
namespace {name}.application.abstractions.email;
public interface IEmailService
{
Task SendAsync(
string to,
string subject,
string htmlBody,
CancellationToken cancellationToken = default);
Task SendTemplateAsync(
string to,
string templateId,
object templateData,
CancellationToken cancellationToken = default);
Task SendWelcomeEmailAsync(
string to,
string userName,
string language = "en",
CancellationToken cancellationToken = default);
Task SendPasswordResetEmailAsync(
string to,
string resetCode,
string language = "en",
CancellationToken cancellationToken = default);
Task SendAssessmentReadyEmailAsync(
string to,
string userName,
string assessmentName,
string language = "en",
CancellationToken cancellationToken = default);
}
模板:邮件选项
// src/{name}.infrastructure/Email/EmailOptions.cs
namespace {name}.infrastructure.email;
public sealed class EmailOptions
{
public const string SectionName = "Email";
public string ApiKey { get; init; } = string.Empty;
public string FromEmail { get; init; } = string.Empty;
public string FromName { get; init; } = string.Empty;
public bool EnableSending { get; init; } = true;
// SendGrid template IDs
public EmailTemplateIds Templates { get; init; } = new();
}
public sealed class EmailTemplateIds
{
public string WelcomeEn { get; init; } = string.Empty;
public string WelcomeEs { get; init; } = string.Empty;
public string PasswordResetEn { get; init; } = string.Empty;
public string PasswordResetEs { get; init; } = string.Empty;
public string AssessmentReadyEn { get; init; } = string.Empty;
public string AssessmentReadyEs { get; init; } = string.Empty;
}
模板:SendGrid 实现
// src/{name}.infrastructure/Email/SendGridEmailService.cs
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SendGrid;
using SendGrid.Helpers.Mail;
using {name}.application.abstractions.email;
namespace {name}.infrastructure.email;
internal sealed class SendGridEmailService : IEmailService
{
private readonly ISendGridClient _client;
private readonly EmailOptions _options;
private readonly ILogger<SendGridEmailService> _logger;
public SendGridEmailService(
IOptions<EmailOptions> options,
ILogger<SendGridEmailService> logger)
{
_options = options.Value;
_client = new SendGridClient(_options.ApiKey);
_logger = logger;
}
public async Task SendAsync(
string to,
string subject,
string htmlBody,
CancellationToken cancellationToken = default)
{
if (!_options.EnableSending)
{
_logger.LogInformation(
"Email sending disabled. Would send to {To}: {Subject}",
to, subject);
return;
}
var message = new SendGridMessage
{
From = new EmailAddress(_options.FromEmail, _options.FromName),
Subject = subject,
HtmlContent = htmlBody
};
message.AddTo(new EmailAddress(to));
var response = await _client.SendEmailAsync(message, cancellationToken);
if (!response.IsSuccessStatusCode)
{
var body = await response.Body.ReadAsStringAsync(cancellationToken);
_logger.LogError(
"Failed to send email to {To}. Status: {Status}, Body: {Body}",
to, response.StatusCode, body);
throw new EmailSendException($"Failed to send email: {response.StatusCode}");
}
_logger.LogInformation("Email sent to {To}: {Subject}", to, subject);
}
public async Task SendTemplateAsync(
string to,
string templateId,
object templateData,
CancellationToken cancellationToken = default)
{
if (!_options.EnableSending)
{
_logger.LogInformation(
"Email sending disabled. Would send template {TemplateId} to {To}",
templateId, to);
return;
}
var message = new SendGridMessage
{
From = new EmailAddress(_options.FromEmail, _options.FromName),
TemplateId = templateId
};
message.AddTo(new EmailAddress(to));
message.SetTemplateData(templateData);
var response = await _client.SendEmailAsync(message, cancellationToken);
if (!response.IsSuccessStatusCode)
{
_logger.LogError(
"Failed to send template email to {To}. Template: {TemplateId}",
to, templateId);
throw new EmailSendException($"Failed to send email: {response.StatusCode}");
}
_logger.LogInformation(
"Template email sent to {To}. Template: {TemplateId}",
to, templateId);
}
public async Task SendWelcomeEmailAsync(
string to,
string userName,
string language = "en",
CancellationToken cancellationToken = default)
{
var templateId = language.ToLower() switch
{
"es" => _options.Templates.WelcomeEs,
_ => _options.Templates.WelcomeEn
};
await SendTemplateAsync(to, templateId, new
{
user_name = userName,
login_url = "https://app.example.com/login"
}, cancellationToken);
}
public async Task SendPasswordResetEmailAsync(
string to,
string resetCode,
string language = "en",
CancellationToken cancellationToken = default)
{
var templateId = language.ToLower() switch
{
"es" => _options.Templates.PasswordResetEs,
_ => _options.Templates.PasswordResetEn
};
await SendTemplateAsync(to, templateId, new
{
reset_code = resetCode,
reset_url = $"https://app.example.com/reset-password?code={resetCode}",
expiration_hours = 24
}, cancellationToken);
}
public async Task SendAssessmentReadyEmailAsync(
string to,
string userName,
string assessmentName,
string language = "en",
CancellationToken cancellationToken = default)
{
var templateId = language.ToLower() switch
{
"es" => _options.Templates.AssessmentReadyEs,
_ => _options.Templates.AssessmentReadyEn
};
await SendTemplateAsync(to, templateId, new
{
user_name = userName,
assessment_name = assessmentName,
assessment_url = "https://app.example.com/assessments"
}, cancellationToken);
}
}
public class EmailSendException : Exception
{
public EmailSendException(string message) : base(message) { }
}
模板:领域事件处理程序
// src/{name}.application/Users/EventHandlers/UserCreatedSendWelcomeEmailHandler.cs
using MediatR;
using Microsoft.Extensions.Logging;
using {name}.application.abstractions.email;
using {name}.domain.users.events;
namespace {name}.application.users.eventhandlers;
internal sealed class UserCreatedSendWelcomeEmailHandler
: INotificationHandler<UserCreatedDomainEvent>
{
private readonly IEmailService _emailService;
private readonly IUserRepository _userRepository;
private readonly ILogger<UserCreatedSendWelcomeEmailHandler> _logger;
public UserCreatedSendWelcomeEmailHandler(
IEmailService emailService,
IUserRepository userRepository,
ILogger<UserCreatedSendWelcomeEmailHandler> logger)
{
_emailService = emailService;
_userRepository = userRepository;
_logger = logger;
}
public async Task Handle(
UserCreatedDomainEvent notification,
CancellationToken cancellationToken)
{
var user = await _userRepository.GetByIdAsync(
notification.UserId,
cancellationToken);
if (user is null)
{
_logger.LogWarning(
"User {UserId} not found for welcome email",
notification.UserId);
return;
}
await _emailService.SendWelcomeEmailAsync(
user.Email.Value,
user.Name,
user.PreferredLanguage,
cancellationToken);
}
}
模板:注册
// appsettings.json
{
"Email": {
"ApiKey": "SG.your-sendgrid-api-key",
"FromEmail": "[email protected]",
"FromName": "My Application",
"EnableSending": true,
"Templates": {
"WelcomeEn": "d-xxxxxxxxxxxxx",
"WelcomeEs": "d-xxxxxxxxxxxxx",
"PasswordResetEn": "d-xxxxxxxxxxxxx",
"PasswordResetEs": "d-xxxxxxxxxxxxx"
}
}
}
// DependencyInjection.cs
services.Configure<EmailOptions>(configuration.GetSection(EmailOptions.SectionName));
services.AddScoped<IEmailService, SendGridEmailService>();
相关技能
dotnet-domain-events-generator- 根据事件触发邮件dotnet-quartz-background-jobs- 计划发送邮件的作业dotnet-outbox-pattern- 可靠的邮件送达
评论
支持 Markdown