/// <summary> /// Send mail and awaits until all messages are sent /// </summary> /// <param name="writer">Writer</param> /// <param name="result">Mail result to send</param> /// <param name="endPoint">End point</param> /// <param name="dispose">Whether to dispose result when done</param> /// <param name="prepMessage">Allow changing mime message right before it is sent</param> /// <returns>Task</returns> private async Task SendMail(StreamWriter writer, MailFromResult result, IPEndPoint endPoint, bool dispose, Action <MimeMessage> prepMessage) { try { // send all emails in one shot for each domain in order to batch DateTime start = DateTime.UtcNow; int count = 0; List <Task> tasks = new List <Task>(); foreach (var group in result.ToAddresses) { tasks.Add(SendMailInternal(writer, result.BackingFile, result.From, group.Key, group.Value, endPoint, null)); count++; } await Task.WhenAll(tasks); MailDemonLog.Info("Sent {0} batches of messages in {1:0.00} seconds", count, (DateTime.UtcNow - start).TotalSeconds); } catch (Exception ex) { MailDemonLog.Error(ex); } finally { if (dispose) { result.Dispose(); } } }
/// <summary> /// Send mail and kicks off the send in a new task /// </summary> /// <param name="foundUser">Found user</param> /// <param name="reader">Reader</param> /// <param name="writer">Writer</param> /// <param name="line">Line</param> /// <param name="endPoint">End point</param> /// <param name="prepMessage">Allow changing mime message right before it is sent</param> /// <returns>Task</returns> private async Task SendMail(MailDemonUser foundUser, Stream reader, StreamWriter writer, string line, IPEndPoint endPoint, Action <MimeMessage> prepMessage) { MailFromResult result = await ParseMailFrom(foundUser, reader, writer, line, endPoint); SendMail(writer, result, endPoint, true, prepMessage).GetAwaiter(); await writer.WriteLineAsync($"250 2.1.0 OK"); await writer.FlushAsync(); }
private async Task <bool> ReceiveMail(Stream reader, StreamWriter writer, string line, IPEndPoint endPoint) { IPHostEntry entry = await Dns.GetHostEntryAsync(endPoint.Address); MailFromResult result = await ParseMailFrom(null, reader, writer, line, endPoint); if (result is null) { return(false); } try { string subject; MimeMessage msg; using (Stream stream = File.OpenRead(result.BackingFile)) { msg = await MimeMessage.LoadAsync(stream, true, cancelToken); subject = msg.Subject; } subject = (subject ?? string.Empty).Trim(); if (subject.Equals("unsubscribe", StringComparison.OrdinalIgnoreCase)) { UnsubscribeHandler?.Invoke(result.From.Address, subject, msg.HtmlBody); return(true); } // mail demon doesn't have an inbox yet, only forwarding, so see if any of the to addresses can be forwarded foreach (var kv in result.ToAddresses) { foreach (MailboxAddress address in kv.Value) { MailDemonUser user = users.FirstOrDefault(u => u.MailAddress.Address.Equals(address.Address, StringComparison.OrdinalIgnoreCase)); // if no user or the forward address points to a user, fail if (user == null || users.FirstOrDefault(u => u.MailAddress.Address.Equals(user.ForwardAddress.Address, StringComparison.Ordinal)) != null) { await writer.WriteLineAsync($"500 invalid command - user not found"); await writer.FlushAsync(); } // setup forward headers MailboxAddress forwardToAddress = (user.ForwardAddress ?? globalForwardAddress); if (forwardToAddress == null) { await writer.WriteLineAsync($"500 invalid command - user not found 2"); await writer.FlushAsync(); } else { string forwardDomain = forwardToAddress.Address.Substring(forwardToAddress.Address.IndexOf('@') + 1); // create new object to forward on MailFromResult newResult = new MailFromResult { BackingFile = result.BackingFile, From = user.MailAddress, ToAddresses = new Dictionary <string, IEnumerable <MailboxAddress> > { { forwardDomain, new List <MailboxAddress> { forwardToAddress } } } }; // forward the message on and clear the forward headers MailDemonLog.Info("Forwarding message, from: {0}, to: {1}, forward: {2}", result.From, address, forwardToAddress); result.BackingFile = null; // we took ownership of the file // send in background SendMail(newResult, true, prepMsg => { prepMsg.Subject = $"FW from {result.From}: {prepMsg.Subject}"; prepMsg.Cc.Clear(); prepMsg.Bcc.Clear(); }, false).ConfigureAwait(false).GetAwaiter(); return(true); // only forward to the first valid address } } } } finally { result.Dispose(); } return(true); }