본문으로 건너뛰기
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(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

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 / 1,000자

이 양식은 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의 스킬 모두 보기

이 스킬의 문제 신고