Эта статья предназначена для новичков и рассматривает микросервис отправки писем через smtp Yandex с использованием .Net Web Api и MailKit. В ней будет рассмотрено, как настроить и использовать данный микросервис для отправки писем с помощью почтового сервиса через существующий почтовый ящик Яндекса.
Ссылки для понимания и работы
Дерево проекта:
```csharp
public class MailSettings {
public string? DisplayName { get; set; }
public string? From { get; set; }
public string? UserName { get; set; }
public string? Password { get; set; }
public string? Host { get; set; }
public int Port { get; set; }
public bool UseSSL { get; set; }
public bool UseStartTls { get; set; }
public bool UseOAuth { get; set; }
}
В данном коде определен класс MailSettings
, который представляет настройки для отправки писем через smtp Yandex с использованием .Net Web Api и MailKit. В классе определены различные свойства, такие как DisplayName
, From
, UserName
, Password
, Host
, Port
, UseSSL
, UseStartTls
и UseOAuth
, которые позволяют задать соответствующие параметры для подключения и аутентификации к почтовому серверу Яндекса.
namespace mailService.Models;
public class MailData {
// Receiver
public List<string> To { get; }
public List<string> Bcc { get; }
public List<string> Cc { get; }
// Sender
public string? From { get; }
public string? DisplayName { get; }
public string? ReplyTo { get; }
public string? ReplyToName { get; }
// Content
public string Subject { get; }
public string? Body { get; }
public MailData(
List<string> to,
string subject,
string? body = null,
string? from = null,
string? displayName = null,
string? replyTo = null,
string? replyToName = null,
List<string>? bcc = null,
List<string>? cc = null
) {
// Receiver
To = to;
Bcc = bcc ?? new List<string>();
Cc = cc ?? new List<string>();
// Sender
From = from;
DisplayName = displayName;
ReplyTo = replyTo;
ReplyToName = replyToName;
// Content
Subject = subject;
Body = body;
}
}
Класс MailData
представляет собой структуру данных для отправки электронных писем через smtp Yandex с использованием .Net Web Api и MailKit. Он имеет различные свойства, такие как To
, Bcc
, Cc
, From
, DisplayName
, ReplyTo
, ReplyToName
, Subject
и Body
, которые позволяют указать необходимую информацию для письма, такую как получатели, отправитель, тема и содержание.
```csharp
public interface IMailService
{
Task<bool> SendAsync(MailData mailData, CancellationToken ct);
}
```
Интерфейс IMailService
определяет контракт для работы с отправкой писем через smtp Yandex с использованием .Net Web Api и MailKit. Метод SendAsync
принимает объект MailData
и токен отмены (CancellationToken
) в качестве параметров и возвращает задачу (Task
) с логическим значением (bool
), указывающим, было ли письмо успешно отправлено. Этот интерфейс позволяет абстрагироваться от конкретной реализации отправки писем и обеспечивает гибкость в дальнейшей работе с отправкой писем.
using MailKit.Net.Smtp;
using MailKit.Security;
using Microsoft.Extensions.Options;
using MimeKit;
using mailService.Configuration;
using mailService.Models;
namespace mailService.Services {
public class MailService: IMailService {
private readonly MailSettings _settings;
public MailService(IOptions<MailSettings> settings) {
_settings = settings.Value;
}
public async Task<bool> SendAsync(MailData mailData) {
try {
// Initialize a new instance of the MimeKit.MimeMessage class
var mail = new MimeMessage();
#region Sender / Receiver
// Sender
mail.From.Add(new MailboxAddress(_settings.DisplayName, mailData.From ?? _settings.From));
mail.Sender = new MailboxAddress(mailData.DisplayName ?? _settings.DisplayName, mailData.From ?? _settings.From);
// Receiver
foreach (string mailAddress in mailData.To)
mail.To.Add(MailboxAddress.Parse(mailAddress));
// Set Reply to if specified in mail data
if(!string.IsNullOrEmpty(mailData.ReplyTo))
mail.ReplyTo.Add(new MailboxAddress(mailData.ReplyToName, mailData.ReplyTo));
// BCC
// Check if a BCC was supplied in the request
if (mailData.Bcc != null)
{
// Get only addresses where value is not null or with whitespace. x = value of address
foreach (string mailAddress in mailData.Bcc.Where(x => !string.IsNullOrWhiteSpace(x)))
mail.Bcc.Add(MailboxAddress.Parse(mailAddress.Trim()));
}
// CC
// Check if a CC address was supplied in the request
if (mailData.Cc != null)
{
foreach (string mailAddress in mailData.Cc.Where(x => !string.IsNullOrWhiteSpace(x)))
mail.Cc.Add(MailboxAddress.Parse(mailAddress.Trim()));
}
#endregion
#region Content
var body = new BodyBuilder();
mail.Subject = mailData.Subject;
body.HtmlBody = mailData.Body;
mail.Body = body.ToMessageBody();
#endregion
#region Send Mail
using (var client = new SmtpClient()) {
try {
await client.ConnectAsync(_settings.Host, _settings.Port, SecureSocketOptions.SslOnConnect);
client.AuthenticationMechanisms.Remove("XOAUTH2");
client.Authenticate(_settings.UserName, _settings.Password);
await client.SendAsync(mail);
}
catch (Exception e) {
Console.WriteLine(e);
throw;
}
finally {
await client.DisconnectAsync(true);
client.Dispose();
}
}
#endregion
return true;
}
catch (Exception) {
return false;
}
}
}
}
Этот код представляет собой службу для отправки электронной почты с использованием библиотеки MailKit. Он включает в себя класс MailService , который реализует интерфейс IMailService .
В конструкторе класса MailService используется зависимость IOptions<MailSettings> settings , чтобы получить настройки электронной почты из конфигурации.
Метод SendAsync асинхронно отправляет электронное письмо на основе переданных данных. Он создает экземпляр класса MimeMessage и настраивает отправителя, получателя, копию, скрытую копию, тему и тело письма. Затем он использует SmtpClient для подключения к серверу SMTP, аутентификации и отправки письма.
Если отправка прошла успешно, метод возвращает true , в противном случае - false .
using mailService.Models;
using mailService.Services;
using Microsoft.AspNetCore.Mvc;
namespace mailService.Controllers;
[Route("api/[controller]")]
[ApiController]
public class MailController: ControllerBase {
private readonly IMailService _mail;
public MailController(IMailService mail)
{
_mail = mail;
}
[HttpPost("sendmail")]
public async Task<IActionResult> SendMailAsync(MailData mailData)
{
bool result = await _mail.SendAsync(mailData);
if (result)
{
return StatusCode(StatusCodes.Status200OK, "Mail has successfully been sent.");
}
else
{
return StatusCode(StatusCodes.Status500InternalServerError, "An error occured. The Mail could not be sent.");
}
}
}
В данном коде представлен контроллер MailController
, который отвечает за обработку HTTP-запросов для отправки электронной почты. Контроллер принимает зависимость IMailService
в конструкторе и содержит метод SendMailAsync
, отвечающий за отправку письма.
Метод SendMailAsync
является действием, доступным по HTTP-маршруту /api/mail/sendmail
и принимает объект MailData
в качестве параметра. Внутри метода вызывается метод SendAsync
зависимости IMailService
для отправки письма. В зависимости от результата отправки, метод возвращает соответствующий статус HTTP и сообщение о результате операции.
Контроллер помечен атрибутами [Route("api/[controller]")]
и [ApiController]
, которые определяют префикс маршрута для контроллера и обеспечивают автоматическую валидацию модели запроса.
Таким образом, данный код представляет собой контроллер API для отправки электронной почты, который использует сервис IMailService
для выполнения операции отправки.
using mailService.Configuration;
using mailService.Services;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.Configure<MailSettings>(
builder
.Configuration
.GetSection(nameof(MailSettings))
);
builder.Services.AddTransient<IMailService, MailService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment()) {
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Здесь настраивается конфигурация приложения ASP.NET Core. В данном коде применяются следующие настройки:
AddControllers()
- добавляет контроллеры в контейнер зависимостей.AddEndpointsApiExplorer()
- добавляет поддержку Swagger/OpenAPI для документирования API.AddSwaggerGen()
- добавляет поддержку Swagger для генерации документации API.Configure<MailSettings>()
- конфигурирует настройки почты из секцииMailSettings
в файле конфигурации.AddTransient<IMailService, MailService>()
- регистрирует сервисIMailService
с временным временем жизни, чтобы он мог быть использован в приложении.
Далее создается экземпляр приложения app
и настраивается конвейер обработки HTTP-запросов. В зависимости от окружения (разработка или другое), применяются соответствующие настройки, такие как использование Swagger. Затем добавляются промежуточные компоненты для перенаправления HTTPS, авторизации и маршрутизации запросов к контроллерам. Наконец, приложение запускается с помощью app.Run()
.
Для запуска приложения, исходные данные берутся из файлов appsettings.Development.json
и appsettings.json
. Эти файлы содержат конфигурационные значения, такие как настройки почты, которые используются для настройки приложения. В файле appsettings.Development.json
содержатся значения, применяемые во время разработки, в то время как appsettings.json
содержит значения для других окружений, таких как продакшн. Эта структура конфигурации позволяет легко настраивать приложение в различных средах без необходимости изменения исходного кода. Для того что бы мои данные остались моими я приведу картинки из этих файлов, они одинаковые у меня.
Откуда взять пароль? Читайте с документации Яндекс
Тэстовые данные для отправки
{
"to": [
"shadow-greg@yandex.ru"
],
"bcc": [
"gregosetrov@yandex.ru"
],
"cc": [
"gregosetrov@yandex.ru"
],
"from": "gregosetrov@yandex.ru",
"displayName": "greg",
"replyTo": "",
"replyToName": "string",
"subject": "string",
"body": "string"
}
Пожалуйста, обратите внимание, что все поля с адресами электронной почты (to, bcc, cc, from) должны быть заполнены при отправке письма. В противном случае, сервер может вернуть ошибку 500.
Заключение:
В данной статье был представлен код для создания микросервиса отправки писем через SMTP Yandex с использованием библиотеки MailKit в .NET Web API. Были показаны основные компоненты кода, такие как класс MailService
для отправки писем, контроллер MailController
для обработки HTTP-запросов и настройка конфигурации приложения.
Код предоставляет пример реализации функциональности отправки писем с помощью библиотеки MailKit и демонстрирует использование интерфейсов и зависимостей для обеспечения расширяемости и тестируемости. Были рассмотрены основные шаги отправки письма, включая создание объекта MimeMessage
, настройку отправителя и получателя, подключение к серверу SMTP и отправку письма.
Также были предоставлены примеры данных для тестирования отправки писем, а также информация о том, откуда можно получить пароль для авторизации при использовании почты Яндекс.
В целом, представленный код и описание позволяют разработчикам создать микросервис для отправки писем с использованием SMTP Yandex и библиотеки MailKit в .NET Web API. Это может быть полезно для различных проектов, где требуется отправка электронных писем из приложения.
Комментарии (16)
ShadowGreg Автор
01.11.2023 04:36Да это тоже вариант, но задача - сделать микросервис который работает с Rabbit и на него ещё накручу пару функций своих, поэтому .Net.
lair
01.11.2023 04:36В смысле - работающий с раббит? Читающий оттуда? Или пишущий? Или что? Какая при этом связь с .Net (ну, кроме того, что вам, видимо, удобнее на нем писать)?
ShadowGreg Автор
01.11.2023 04:36Читающий. А микросервис работающий на API можно и без .Net организовать, но думаю это будет более изощрённо и не так поворотливо.
AirLight
01.11.2023 04:36У меня на проекте примерно такой же, только с разными провайдерами работает, в том числе Mailchimp, и нет контроллеров, только потребляет сообщения из очереди.
Ksey_N
01.11.2023 04:36Для запуска приложения, исходные данные берутся из файлов
appsettings.Development.json
иappsettings.json
. Эти файлы содержат конфигурационные значения, такие как настройки почты, которые используются для настройки приложения. В файлеappsettings.Development.json
содержатся значения, применяемые во время разработки, в то время какappsettings.json
содержит значения для других окружений, таких как продакшнНу вообще не совсем так.
Конфигурацию строит специальный билдер, она собирается в следующем порядке:
1.appsetting.json
2.appsetting.{ТЕКУЩЕЕ ОКРУЖЕНИЕ}.json
3.переменные окруженияИсходя из порядка по умолчанию appsetting.{ТЕКУЩЕЕ ОКРУЖЕНИЕ}.json переопределяет или создаёт конфигурационную секцию, которая будет использоваться только в текущем окружении, a appsetting.json содержит общую конфигурацию, которая будет использоваться в любом окружении.
Как рекомендация, данные, которые могут быть скомпрометированы (здесь это логин и пароль от смтп-клиента) лучше хранить именно в переменных окружения, чтобы усложнить их учетку к третьим лицам.
П.С.
Тэстовые данные для отправки
Мои глаза истекли кровью, на этом предложении)))
OwDafuq
01.11.2023 04:36Почему проект называется в lowerCamelCase?
-
using (var client = new SmtpClient())
, а потомclient.Dispose();
, а зачем Dispose?Давно уже существует конструкция
using var
, имхо, но для глаза приятнее
Какие-то магические Bcc и Cc в конструкторе, что они означают? (если комментарии в полном коде у конструктора есть, то вопрос отпадает)
-
Не придирка, а просто вопросы:
не пробовали все файлы настроек складывать в отдельную папку? Имхо, но мне показалось это в разы удобнее, чем когда они находятся в корне.
не пробовали разделение настроек, чтобы не все в appsettings.json складывать? я обычно такие вещи разбиваю на логические куски, например: SerilogSettings.json (настройки для Serilog'a), MongoSettings.json (настройки для MongoDB), etc. В вашем примере хорошо (ИМХО!!!) подошло разделение конфига: appsettings.json, mailsettings.json
Vitimbo
01.11.2023 04:36Не совсем магические, если учитывать контекст. Это отправка копий письма дополнительным адресатам, но да, лучше бы добавить комментарий, чтобы не было вопросов.
lair
01.11.2023 04:36...вот я вижу у вас в коде
UseAuthorization
. А зачем? У вас есть авторизация? На чем она основана?Или вот зачем вам сваггер?
Пожалуйста, обратите внимание, что все поля с адресами электронной почты (to, bcc, cc, from) должны быть заполнены при отправке письма. В противном случае, сервер может вернуть ошибку 500.
А почему 500, если это совершенно банальная ошибка валидации входных данных?
ShadowGreg Автор
01.11.2023 04:36Клёво спасибо за то что заметили что можно доработать! 500 - потому севак яндекса выдавал такую ошибку в дебагере на сколько я помню.
lair
01.11.2023 04:36500 - потому севак яндекса выдавал такую ошибку в дебагере на сколько я помню.
Так почему у вас невалидные данные вообще отправляются в Яндекс?
lair
01.11.2023 04:36...или вот у вас есть настройка:
различные свойства, такие как [...] UseOAuth, которые позволяют задать соответствующие параметры для подключения и аутентификации к почтовому серверу Яндекса.
И я такой: хм, интересно, как у вас там OAuth сделан. А потом выясняется, что настройка-то нигде не используется, зато:
client.AuthenticationMechanisms.Remove("XOAUTH2");
А пароль при этом, небось, лежит в плейнтексте, потому что работы с секретами я у вас тоже не вижу.
Грустно это всё.
nronnie
01.11.2023 04:36Метод
MailService.SendAsync
нарушает сразу два принципа - "Single Responsibility" и "Dependency Inversion". Он делает сразу две вещи - создаетMimeMessage
изMailData
и отправляет его вSmtpClient
, и делает это обращаясь к нему напрямую (new SmtpClient()
), а не через абстракцию. На практике результатом будет то, что, например, невозможно будет оттестировать созданиеMimeMessage
не отправляя при этом его в "настоящий" SMTP.
vasiliy_stankevich
Как всегда привязка к Asp .Net приложению цветет и пахнет.... А если убрать эту привязку то вполне можно обойтись 1 сущностью для работы с e-mail, которую кстати очень удобно будет интегрировать в DI контейнер ????