跳到正文
cccskills
免费在 GitHub 公开

dotnet-email-service-sendgrid

Implements email service abstraction with SendGrid provider. Includes template support, localization, async sending, and domain event integration for transactional emails.

中文简介正在准备中。当前显示原文说明。

查看安装方法

包含的文件(1)

  • SKILL.md10.1 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

Email Service Integration

Overview

Abstracted email service with template support:

  • Interface in Application - IEmailService
  • SendGrid implementation - Production-ready provider
  • Template support - Dynamic content with placeholders
  • Localization - Multiple language templates
  • Domain event integration - Send on user actions

Quick Reference

ComponentPurpose
IEmailServiceEmail abstraction interface
SendGridEmailServiceSendGrid implementation
EmailOptionsConfiguration settings
EmailTemplateTemplate definitions

Template: Email Service Interface

// 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);
}

Template: Email Options

// 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;
}

Template: SendGrid Implementation

// 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) { }
}

Template: Domain Event Handler

// 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);
    }
}

Template: Registration

// appsettings.json
{
  "Email": {
    "ApiKey": "SG.your-sendgrid-api-key",
    "FromEmail": "noreply@example.com",
    "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>();

Related Skills

  • dotnet-domain-events-generator - Trigger emails from events
  • dotnet-quartz-background-jobs - Scheduled email jobs
  • dotnet-outbox-pattern - Reliable email delivery

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

Generates audit trail infrastructure for entities. Implements IAuditable interface, EF Core SaveChanges interceptor, and automatic population of CreatedAt, UpdatedAt, CreatedBy, and UpdatedBy fields.

中文简介正在准备中。当前显示原文说明。

ronnythedev/dotnet-clean-architecture-skills762026年7月14日 更新

Scaffolds 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.

中文简介正在准备中。当前显示原文说明。

ronnythedev/dotnet-clean-architecture-skills762026年7月14日 更新

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.

中文简介正在准备中。当前显示原文说明。

ronnythedev/dotnet-clean-architecture-skills762026年7月14日 更新

Generates CQRS Queries with Handlers and Response DTOs for read operations. Uses Dapper for optimized read queries, bypassing the domain model for better performance.

中文简介正在准备中。当前显示原文说明。

ronnythedev/dotnet-clean-architecture-skills762026年7月14日 更新

Generates optimized read queries using Dapper. Includes multi-mapping for joins, pagination, dynamic filtering, CTEs, and best practices for high-performance data access.

中文简介正在准备中。当前显示原文说明。

ronnythedev/dotnet-clean-architecture-skills762026年7月14日 更新

Generates Domain Entities following DDD principles with factory methods, private setters, domain events, and proper encapsulation. Supports aggregate roots, child entities, and value objects.

中文简介正在准备中。当前显示原文说明。

ronnythedev/dotnet-clean-architecture-skills762026年7月14日 更新

查看 ronnythedev 的全部技能

报告此技能的问题