public DeliverAsync (
|
||
message | ||
recipients | List |
|
return | Task
|
public async Task<List<DeliveryResult>> DeliverAsync(Message message, List<Recipient> recipients)
{
if (message == null)
throw new ArgumentNullException(nameof(message));
if (message.Recipients == null)
throw new ArgumentNullException(nameof(message.Recipients));
var commaSeparatedRecipientList = string.Join(", ", recipients.Select(item => item.Address));
_log.LogInfo(new LogEvent()
{
EventType = LogEventType.Application,
LogLevel = LogLevel.Info,
Message = $"Delivering message from {message.From} to {commaSeparatedRecipientList}",
Protocol = "SMTPD",
});
var recipientsByDomain =
recipients.GroupBy(recipient => EmailAddressParser.GetDomain(recipient.Address)).Distinct().ToList();
var result = new List<DeliveryResult>();
foreach (var domainWithRecipients in recipientsByDomain)
{
var ipAddresses = await _dnsClient.ResolveMxIpAddressesAsync(domainWithRecipients.Key);
var remainingRecipientsOnDomain = domainWithRecipients.Select(item => item.Address).ToList();
foreach (var ipAddress in ipAddresses)
{
var client = new TcpClient();
await client.ConnectAsync(ipAddress, 25);
var connection = new Connection(client, CancellationToken.None);
using (var messageStream = _messageRepository.GetMessageData(message))
{
var messageData = new MessageData()
{
From = message.From,
Recipients = remainingRecipientsOnDomain,
Data = messageStream
};
var clientSession = new SmtpClientSession(_log, new SmtpClientSessionConfiguration(), messageData);
await clientSession.HandleConnection(connection);
foreach (var deliveryResult in clientSession.DeliveryResult)
{
var matchingRecipient =
recipients.Single(recipient => string.Equals(recipient.Address, deliveryResult.Recipient,
StringComparison.InvariantCultureIgnoreCase));
switch (deliveryResult.ReplyCodeSeverity)
{
case ReplyCodeSeverity.Positive:
// Delete the recipient right away, so that if there is a crash we don't end up sending to this recipient again.
await _messageRepository.DeleteRecipientAsync(matchingRecipient);
result.Add(deliveryResult);
remainingRecipientsOnDomain.Remove(deliveryResult.Recipient);
_log.LogInfo(new LogEvent()
{
EventType = LogEventType.Application,
LogLevel = LogLevel.Info,
Message = $"Message delivery from {message.From} to {deliveryResult.Recipient} completed",
Protocol = "SMTPD",
});
break;
case ReplyCodeSeverity.PermanentNegative:
// Let this recipient be deleted after we've submitted bounce message. This is delayed so that we don't
// lose the information if there's a crash.
result.Add(deliveryResult);
remainingRecipientsOnDomain.Remove(deliveryResult.Recipient);
_log.LogInfo(new LogEvent()
{
EventType = LogEventType.Application,
LogLevel = LogLevel.Info,
Message = $"Message delivery from {message.From} to {deliveryResult.Recipient} failed permanently: {deliveryResult.ResultMessage}",
Protocol = "SMTPD",
});
break;
case ReplyCodeSeverity.TransientNegative:
_log.LogInfo(new LogEvent()
{
EventType = LogEventType.Application,
LogLevel = LogLevel.Info,
Message = $"Message delivery from {message.From} to {deliveryResult.Recipient} failed temporarily: {deliveryResult.ResultMessage}",
Protocol = "SMTPD",
});
break;
}
}
}
if (!remainingRecipientsOnDomain.Any())
{
// No more recipients remaining.
break;
}
}
}
return result;
}
}
private async Task DeliverMessageAsync(Message message) { var accountRepository = _container.GetInstance <IAccountRepository>(); var messageRepository = _container.GetInstance <IMessageRepository>(); var folderRepository = _container.GetInstance <IFolderRepository>(); var dnsClient = _container.GetInstance <IDnsClient>(); message.NumberOfDeliveryAttempts++; bool isLastAttempt = message.NumberOfDeliveryAttempts >= 3; var deliveryResults = new List <DeliveryResult>(); try { var remainingRecipients = new List <Recipient>(message.Recipients); var localDelivery = new LocalDelivery(accountRepository, messageRepository, folderRepository, _log); deliveryResults.AddRange(await localDelivery.DeliverAsync(message, remainingRecipients.Where(recipient => recipient.AccountId != 0).ToList())); var externalDelivery = new ExternalDelivery(messageRepository, dnsClient, _log); deliveryResults.AddRange(await externalDelivery.DeliverAsync(message, remainingRecipients.Where(recipient => recipient.AccountId == 0).ToList())); var failedRecipients = deliveryResults.Where(result => result.ReplyCodeSeverity == ReplyCodeSeverity.PermanentNegative || (isLastAttempt && result.ReplyCodeSeverity == ReplyCodeSeverity.TransientNegative)); await SubmitBounceMessageAsync(message, failedRecipients); var deliveryCompleted = deliveryResults.Any(result => result.ReplyCodeSeverity == ReplyCodeSeverity.TransientNegative); if (isLastAttempt || !deliveryCompleted) { await messageRepository.DeleteAsync(message); } } catch (Exception ex) { var logEvent = new LogEvent() { EventType = LogEventType.Application, LogLevel = LogLevel.Error, Protocol = "SMTPD", }; if (isLastAttempt) { logEvent.Message = "Failed delivering message due to an error. Giving up."; } else { logEvent.Message = "Failed delivering message due to an error. Will retry later."; } _log.LogException(logEvent, ex); if (isLastAttempt) { await messageRepository.DeleteAsync(message); } else { await messageRepository.UpdateAsync(message); } } }