Популярні бібліотеки

Відправка Email з FluentEmail в ASP.NET Core

FluentEmail: fluent-інтерфейс для відправки листів, Razor-шаблони, інтеграція з SMTP та SendGrid, очереди, retry та тестування без реального SMTP.

Відправка Email з FluentEmail в ASP.NET Core

SmtpClient з базового .NET — застарілий, із незручним API та обмеженою підтримкою. Різні email-провайдери (SendGrid, Mailgun, Amazon SES) мають різне API. FluentEmail уніфікує надсилання листів через єдиний fluent-інтерфейс та дозволяє легко перемикати провайдерів.

1. Проблема стандартного SmtpClient

Старий підхід — SmtpClient — антипатерн
// ❌ SmtpClient — не рекомендований Microsoft (obsolete)
using var client = new SmtpClient("smtp.gmail.com", 587)
{
    EnableSsl        = true,
    Credentials      = new NetworkCredential("user@gmail.com", "password"),
    DeliveryMethod   = SmtpDeliveryMethod.Network
};

var message = new MailMessage
{
    From    = new MailAddress("noreply@myapp.com"),
    Subject = "Підтвердження реєстрації",
    Body    = "<h1>Вітаємо!</h1>",  // HTML-тіло — просто рядок
    IsBodyHtml = true
};
message.To.Add("user@example.com");
// Вкладення — окремо
// CC/BCC — окремо
// Заголовки — окремо

await client.SendMailAsync(message);

Незручно, не tree-shakeable, не підтримує сучасні провайдери через нативне API.


2. Встановлення FluentEmail

Встановлення пакетів
# Ядро
dotnet add package FluentEmail.Core

# Провайдери — вибираємо один
dotnet add package FluentEmail.Smtp         # SMTP
dotnet add package FluentEmail.SendGrid     # SendGrid
dotnet add package FluentEmail.Mailgun      # Mailgun

# Рушії шаблонів — вибираємо один
dotnet add package FluentEmail.Razor        # Razor (.cshtml)
dotnet add package FluentEmail.Liquid       # Liquid templates
Program.cs — конфігурація FluentEmail
builder.Services
    .AddFluentEmail("noreply@myapp.com", "MyApp")
    .AddRazorRenderer()            // Рушій шаблонів
    .AddSmtpSender(new SmtpClient  // Або .AddSendGridSender(apiKey)
    {
        Host            = builder.Configuration["Smtp:Host"]!,
        Port            = int.Parse(builder.Configuration["Smtp:Port"]!),
        EnableSsl       = true,
        DeliveryMethod  = SmtpDeliveryMethod.Network,
        UseDefaultCredentials = false,
        Credentials     = new NetworkCredential(
            builder.Configuration["Smtp:Username"]!,
            builder.Configuration["Smtp:Password"]!)
    });

3. Базове відправлення

Простий лист

Services/EmailService.cs — базове використання
using FluentEmail.Core;

public class EmailService
{
    private readonly IFluentEmail _fluentEmail;
    private readonly ILogger<EmailService> _logger;

    // IFluentEmail — Transient сервіс (новий екземпляр на кожен виклик)
    public EmailService(IFluentEmail fluentEmail, ILogger<EmailService> logger)
    {
        _fluentEmail = fluentEmail;
        _logger      = logger;
    }

    public async Task SendWelcomeEmailAsync(string toEmail, string userName)
    {
        var response = await _fluentEmail
            .To(toEmail, userName)           // Одержувач
            .CC("support@myapp.com")         // Копія
            .Subject("Ласкаво просимо до MyApp!")
            .Body($"""
                <h1>Вітаємо, {userName}!</h1>
                <p>Дякуємо за реєстрацію у нашому сервісі.</p>
                <p>Ваш акаунт успішно активований.</p>
                """, isHtml: true)
            .SendAsync();

        if (!response.Successful)
        {
            _logger.LogError(
                "Не вдалося надіслати вітальний лист до {Email}: {Errors}",
                toEmail, string.Join(", ", response.ErrorMessages));
        }
    }
}

Лист з вкладенням

Services/EmailService.cs — вкладення
public async Task SendInvoiceAsync(string toEmail, byte[] pdfContent)
{
    await _fluentEmail
        .To(toEmail)
        .Subject("Ваш рахунок")
        .Body("Будь ласка, знайдіть рахунок у вкладенні.", isHtml: false)
        .AttachFromFilename("/path/to/invoice.pdf",         // З файлу
            attachmentName: "invoice.pdf")
        .Attach(new FluentEmail.Core.Models.Attachment     // З пам'яті
        {
            Filename    = "invoice-copy.pdf",
            ContentType = "application/pdf",
            Data        = new MemoryStream(pdfContent)
        })
        .SendAsync();
}

4. Razor-шаблони для HTML листів

Найпотужніша функція FluentEmail — використання Razor (.cshtml) для рендерингу HTML-листів:

EmailTemplates/WelcomeEmail.cshtml — Razor шаблон
@model WelcomeEmailModel
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <style>
        body  { font-family: Arial, sans-serif; background: #f5f5f5; }
        .card { max-width: 600px; margin: 40px auto; background: white; 
                border-radius: 8px; padding: 40px; }
        .btn  { display: inline-block; padding: 12px 24px;
                background: #3b82f6; color: white; border-radius: 6px;
                text-decoration: none; font-weight: bold; }
    </style>
</head>
<body>
<div class="card">
    <h1>Привіт, @Model.UserName! 👋</h1>
    <p>Дякуємо за реєстрацію на <strong>@Model.AppName</strong>.</p>

    @if (Model.RequiresConfirmation)
    {
        <p>Для активації акаунту натисніть кнопку нижче:</p>
        <a href="@Model.ConfirmationUrl" class="btn">
            Підтвердити Email
        </a>
        <p style="color: #6b7280; font-size: 14px;">
            Посилання дійсне протягом @Model.ExpirationHours годин.
        </p>
    }
    else
    {
        <p>Ваш акаунт вже активований. Ласкаво просимо!</p>
    }

    <hr style="margin: 30px 0;" />
    <p style="color: #6b7280; font-size: 12px;">
        © @DateTime.Now.Year @Model.AppName. Всі права захищені.
    </p>
</div>
</body>
</html>
Models/WelcomeEmailModel.cs
public class WelcomeEmailModel
{
    public string  UserName            { get; set; } = string.Empty;
    public string  AppName             { get; set; } = "MyApp";
    public bool    RequiresConfirmation { get; set; }
    public string? ConfirmationUrl      { get; set; }
    public int     ExpirationHours      { get; set; } = 24;
}
Services/EmailService.cs — відправлення з шаблоном
public async Task SendWelcomeWithTemplateAsync(
    string toEmail,
    WelcomeEmailModel model)
{
    await _fluentEmail
        .To(toEmail, model.UserName)
        .Subject($"Ласкаво просимо до {model.AppName}!")
        // Шлях до .cshtml відносно кореня проєкту
        .UsingTemplateFromFile(
            "~/EmailTemplates/WelcomeEmail.cshtml",
            model)
        .SendAsync();
}

5. Відправлення через SendGrid

Program.cs — конфігурація SendGrid
builder.Services
    .AddFluentEmail("noreply@myapp.com", "MyApp")
    .AddRazorRenderer()
    .AddSendGridSender(
        apiKey:           builder.Configuration["SendGrid:ApiKey"]!,
        sandBoxMode:      builder.Environment.IsDevelopment()); // Не надсилати реально!

SendGrid sandbox mode — листи обробляються, але не надсилаються кінцевому отримувачу. Ідеально для розробки та тестування без ризику надіслати тестові листи реальним користувачам.


6. IFluentEmailFactory: Кілька листів

IFluentEmailFactory дозволяє створювати кілька незалежних листів в одному запиті:

Services/BatchEmailService.cs
using FluentEmail.Core;

public class BatchEmailService
{
    private readonly IFluentEmailFactory _emailFactory;

    public BatchEmailService(IFluentEmailFactory emailFactory)
        => _emailFactory = emailFactory;

    public async Task SendWeeklyDigestAsync(List<Subscriber> subscribers)
    {
        var tasks = subscribers.Select(subscriber =>
            _emailFactory
                .Create()              // Новий незалежний екземпляр
                .To(subscriber.Email, subscriber.Name)
                .Subject("Щотижневий дайджест MyApp")
                .UsingTemplateFromFile(
                    "~/EmailTemplates/WeeklyDigest.cshtml",
                    new DigestModel { Subscriber = subscriber })
                .SendAsync()
        );

        await Task.WhenAll(tasks);
    }
}

7. Тестування без SMTP

Для unit-тестів використовуйте MailKitSimpleSender або NullEmailSender:

Tests/EmailServiceTests.cs
public class EmailServiceTests
{
    [Fact]
    public async Task SendWelcomeEmail_ShouldCallSender()
    {
        // Arrange
        var mockSender = Substitute.For<ISender>();
        mockSender.SendAsync(Arg.Any<IFluentEmail>())
            .Returns(new SendResponse());

        var services = new ServiceCollection()
            .AddFluentEmail("noreply@test.com")
            .AddRazorRenderer();
        services.AddSingleton<ISender>(mockSender);

        var sp    = services.BuildServiceProvider();
        var email = sp.GetRequiredService<IFluentEmail>();

        // Act
        await email
            .To("user@test.com")
            .Subject("Test")
            .Body("Test body")
            .SendAsync();

        // Assert
        await mockSender.Received(1)
            .SendAsync(Arg.Any<IFluentEmail>());
    }
}

Практичні завдання


Резюме

Fluent API

Один ланцюжок методів замість громіздкого MailMessage. Читабельно та без помилок.

Razor шаблони

Повна міць Razor для HTML листів: умовна логіка, цикли, моделі даних.

Провайдери

SMTP, SendGrid, Mailgun — один інтерфейс, легка заміна. Sandbox для розробки.

Посилання: