SmtpClient з базового .NET — застарілий, із незручним API та обмеженою підтримкою. Різні email-провайдери (SendGrid, Mailgun, Amazon SES) мають різне API. FluentEmail уніфікує надсилання листів через єдиний fluent-інтерфейс та дозволяє легко перемикати провайдерів.// ❌ 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.
# Ядро
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
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"]!)
});
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));
}
}
}
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();
}
Найпотужніша функція FluentEmail — використання Razor (.cshtml) для рендерингу HTML-листів:
@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>
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;
}
public async Task SendWelcomeWithTemplateAsync(
string toEmail,
WelcomeEmailModel model)
{
await _fluentEmail
.To(toEmail, model.UserName)
.Subject($"Ласкаво просимо до {model.AppName}!")
// Шлях до .cshtml відносно кореня проєкту
.UsingTemplateFromFile(
"~/EmailTemplates/WelcomeEmail.cshtml",
model)
.SendAsync();
}
builder.Services
.AddFluentEmail("noreply@myapp.com", "MyApp")
.AddRazorRenderer()
.AddSendGridSender(
apiKey: builder.Configuration["SendGrid:ApiKey"]!,
sandBoxMode: builder.Environment.IsDevelopment()); // Не надсилати реально!
SendGrid sandbox mode — листи обробляються, але не надсилаються кінцевому отримувачу. Ідеально для розробки та тестування без ризику надіслати тестові листи реальним користувачам.
IFluentEmailFactory дозволяє створювати кілька незалежних листів в одному запиті:
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);
}
}
Для unit-тестів використовуйте MailKitSimpleSender або NullEmailSender:
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>());
}
}
EmailService.SendPasswordResetAsync(string email, string resetLink).OrderConfirmation.cshtml) з таблицею товарів, загальною сумою та посиланням на відстеження. Відправте лист через IFluentEmail.UsingTemplateFromFile().EmailQueueService з чергою (IQueue або ChannelIHostedService. При помилці SMTP — логуйте та спробуйте ще раз через 5 хвилин.Fluent API
MailMessage. Читабельно та без помилок.Razor шаблони
Провайдери
Посилання:
Feature Management та Feature Flags в ASP.NET Core
Feature Flags у ASP.NET Core: вмикання/вимикання фіч без деплою, Feature Filters (часові, відсоткові, для груп), Azure App Configuration та поступовий rollout.
Генерація PDF з QuestPDF в ASP.NET Core
QuestPDF: layout-based підхід до генерації PDF, компоненти Text, Image, Table, Column/Row, стилізація, динамічні документи та потоковий вивід у ASP.NET Core.