Отправить массовую электронную почту с MailKit в C#
Мы используем MailKit
успешно отправлять электронные письма, создавая клиента, устанавливая соединение и отправляя почту. Очень стандартный материал, который прекрасно работает, когда мы получаем сообщение в нашем приложении, мы что-то делаем и отправляем по электронной почте.
Однако мы хотим иметь возможность обрабатывать десятки тысяч писем как можно быстрее.
Наш целевой почтовый сервер может быть недоступен, и поэтому мы можем быстро создавать резервные копии сообщений, что приводит к отставанию.
С MailKit
Какой лучший и быстрый способ обработки почты, чтобы они отправлялись как можно быстрее. Например, в настоящий момент каждое письмо может обрабатываться одно за другим, и если на обработку каждой из них уходит секунда, отправка 40000 писем может занять много времени.
Мы использовали parallel foreach
раскрутить несколько потоков, но это имеет ограничения. Любые предложения или рекомендации будут оценены.
Добавлен пример кода: ИСПРАВЛЕНИЕ, ДОБАВЛЕНО НОВЫЙ ОБРАЗЕЦ КОДА. Это намного быстрее, но я не могу заставить его работать, каждый раз создавая новое соединение. Exchange выдает ошибки "отправитель уже указан". В настоящее время это в среднем отправляет около 6 писем в секунду.
var rangePartitioner = Partitioner.Create(0, inpList.Count, 15);
var po = new ParallelOptions { MaxDegreeOfParallelism = 30 };
Parallel.ForEach(rangePartitioner, (range, loopState) =>
{
using (var client = new SmtpClient(new SlabProtocolLogger()))
{
client.Connect(_appSettings.RelayAddress, _appSettings.RelayPort);
client.AuthenticationMechanisms.Remove("XOAUTH2");
for (int i = range.Item1; i < range.Item2; i++)
{
var message = _outboundQueueRepository.Read(inpList[i]).Load();
client.Send(message.Body, message.Metadata.Sender, message.Metadata.Recipients.Select(r => (MailboxAddress)r));
_outboundQueueRepository.Remove(inpList[i]);
};
}
});
2 ответа
Поправьте меня, если я ошибаюсь, но мне кажется, что это работает так, что Parallel.Foreach создает некоторое количество потоков. Затем каждый поток создает SMTP-соединение и затем зацикливается для отправки пакета сообщений.
Это кажется довольно разумным для меня.
Единственный совет, который я могу дать вам, который мог бы оптимизировать это больше, - если многие из этих сообщений имеют одинаковое содержание и один и тот же адрес отправителя и единственное различие заключается в том, кто является получателями, вы можете значительно сократить количество сообщений, которые вам нужно Отправить.
Например, если вы в данный момент делаете что-то вроде отправки следующих 3 сообщений:
Сообщение № 1:
From: no-reply@company.com
To: Joe The Plumber <joe@plumbing-masters.com>
Subject: We've got a new sale! 50% off everything in stock!
some message text goes here.
Сообщение № 2
From: no-reply@company.com
To: Sara the Chef <sara@amazing-chefs.com>
Subject: We've got a new sale! 50% off everything in stock!
some message text goes here.
Сообщение № 3:
From: no-reply@company.com
To: Ben the Cobbler <ben@cobblers-r-us.com>
Subject: We've got a new sale! 50% off everything in stock!
some message text goes here.
Ваш код может создать 3 потока, посылая по 1 сообщения в каждом из этих потоков.
Но что, если вместо этого вы создали следующее сообщение:
From: no-reply@company.com
To: undisclosed-recipients:;
Subject: We've got a new sale! 50% off everything in stock!
some message text goes here.
а затем использовал следующий код для отправки всем 3 клиентам одновременно MimeMessage
?
var sender = new MailboxAddress (null, "no-reply@company.com");
var recipients = new List<MailboxAddress> ();
recipients.Add (new MailboxAddress ("Joe the Plumber", "joe@plumbing-masters.com"));
recipients.Add (new MailboxAddress ("Sara the Chef", "sara@amazing-chefs.com"));
recipients.Add (new MailboxAddress ("Ben the Cobbler", "ben@cobblers-r-us.com"));
client.Send (message, sender, recipients);
Все 3 ваших клиента получат одно и то же письмо, и вам не нужно было отправлять 3 сообщения, вам нужно было всего лишь отправить 1 сообщение.
Возможно, вы уже понимаете эту концепцию, так что это может вам совсем не помочь - я просто упомянул ее, потому что заметил, что это не сразу очевидно для всех. Некоторые люди думают, что им нужно отправить 1 сообщение на каждого получателя, и поэтому они оказываются в ситуации, когда они пытаются оптимизировать отправку 1000 сообщений, когда им действительно нужно только 1 отправить.
Таким образом, мы нашли более широкое исправление, которое значительно улучшило производительность в дополнение к улучшениям, которые мы обнаружили в цикле Parrallel ForEach. Не имеет отношения к MailKit, но я думал, что поделюсь в любом случае. Способ, которым наш вызывающий код создавал наш inputList, состоял в том, чтобы использовать DirectoryInfo.GetDirectories для перечисления по всем первым в каталоге. В некоторых случаях нашему коду требовалось 2 секунды для выполнения над каталогом с 40k-файлами в нем. Мы изменили это на EnumerateDirectories, и он эффективно освободил код отправки почты для отправки множества писем.
Таким образом, чтобы подтвердить, параллельный цикл работал отлично, основной проблемой производительности в другом месте было настоящее узкое место.