private static void HandleDequeue() { if (_StartedThreads >= STAGING_DEQUEUE_THREADS) { return; } _StartedThreads++; while (true) { BasicDeliverEventArgs ea = RabbitMq.RabbitMqManager.Dequeue(RabbitMqManager.RabbitMqQueue.InboundStaging, 1, 100).FirstOrDefault(); if (ea == null) { Thread.Sleep(1000); continue; } MtaQueuedMessage qmsg = Serialisation.Deserialise <MtaQueuedMessage>(ea.Body); MtaMessage msg = new MtaMessage(qmsg.ID, qmsg.VirtualMTAGroupID, qmsg.InternalSendID, qmsg.MailFrom, qmsg.RcptTo, string.Empty); RabbitMqManager.Publish(msg, RabbitMqManager.RabbitMqQueue.Inbound, true); RabbitMqManager.Publish(qmsg, RabbitMqManager.RabbitMqQueue.OutboundWaiting, true); RabbitMqManager.Ack(RabbitMqManager.RabbitMqQueue.InboundStaging, ea.DeliveryTag, false); } }
/// <summary> /// Enqueue the message for relaying. /// </summary> /// <param name="msg">Message to enqueue.</param> public static async Task <bool> Enqueue(MtaQueuedMessage msg) { RabbitMqManager.RabbitMqQueue queue = RabbitMqManager.RabbitMqQueue.OutboundWaiting; int secondsUntilNextAttempt = (int)Math.Ceiling((msg.AttemptSendAfterUtc - DateTime.UtcNow).TotalSeconds); if (secondsUntilNextAttempt > 0) { if (secondsUntilNextAttempt < 10) { queue = RabbitMqManager.RabbitMqQueue.OutboundWait1; } else if (secondsUntilNextAttempt < 60) { queue = RabbitMqManager.RabbitMqQueue.OutboundWait10; } else if (secondsUntilNextAttempt < 300) { queue = RabbitMqManager.RabbitMqQueue.OutboundWait60; } else { queue = RabbitMqManager.RabbitMqQueue.OutboundWait300; } } var published = await RabbitMqManager.Publish(msg, queue, priority : msg.RabbitMqPriority); if (published) { msg.IsHandled = true; } return(published); }
private void HandleDequeue() { while (!IsStopping) { BasicDeliverEventArgs ea = RabbitMq.RabbitMqManager.Dequeue(RabbitMqManager.RabbitMqQueue.InboundStaging, 1, 100).FirstOrDefault(); if (ea == null) { //await Task.Delay(1000); System.Threading.Thread.Sleep(1000); continue; } MtaQueuedMessage qmsg = Serialisation.Deserialise <MtaQueuedMessage>(ea.Body).Result; MtaMessage msg = new MtaMessage { ID = qmsg.ID, InternalSendID = qmsg.InternalSendID, MailFrom = qmsg.MailFrom, RcptTo = qmsg.RcptTo, VirtualMTAGroupID = qmsg.VirtualMTAGroupID }; RabbitMqManager.Publish(msg, RabbitMqManager.RabbitMqQueue.Inbound, true, qmsg.RabbitMqPriority).Wait(); RabbitMqManager.Publish(qmsg, RabbitMqManager.RabbitMqQueue.OutboundWaiting, true, qmsg.RabbitMqPriority).Wait(); RabbitMqManager.Ack(RabbitMqManager.RabbitMqQueue.InboundStaging, ea.DeliveryTag, false); } }
/// <summary> /// Discards the message. /// </summary> /// <param name="failMsg"></param> public static async Task <bool> HandleMessageDiscardAsync(MtaQueuedMessage msg) { await MtaTransaction.LogTransactionAsync(msg, TransactionStatus.Discarded, string.Empty, null, null); msg.IsHandled = true; return(true); }
/// <summary> /// Enqueue the message for relaying. /// </summary> /// <param name="msg">Message to enqueue.</param> public static bool Enqueue(MtaQueuedMessage msg) { RabbitMqManager.RabbitMqQueue queue = RabbitMqManager.RabbitMqQueue.OutboundWaiting; int secondsUntilNextAttempt = (int)Math.Ceiling((msg.AttemptSendAfterUtc - DateTime.UtcNow).TotalSeconds); if (secondsUntilNextAttempt > 0) { if (secondsUntilNextAttempt < 10) { queue = RabbitMqManager.RabbitMqQueue.OutboundWait1; } else if (secondsUntilNextAttempt < 60) { queue = RabbitMqManager.RabbitMqQueue.OutboundWait10; } else if (secondsUntilNextAttempt < 300) { queue = RabbitMqManager.RabbitMqQueue.OutboundWait60; } else { queue = RabbitMqManager.RabbitMqQueue.OutboundWait300; } } if (!RabbitMqManager.Publish(msg, queue)) { return(false); } msg.IsHandled = true; return(true); }
/// <summary> /// This method handle successful delivery. /// Logs success /// Deletes queued data /// </summary> public static async Task <bool> HandleDeliverySuccessAsync(MtaQueuedMessage msg, VirtualMTA ipAddress, MXRecord mxRecord, string response) { await MtaTransaction.LogTransactionAsync(msg, TransactionStatus.Success, response, ipAddress, mxRecord); msg.IsHandled = true; return(true); }
/// <summary> /// Enqueue the messages in the collection for relaying. /// </summary> /// <param name="inboundMessages">Messages to enqueue.</param> public static void Enqueue(IList <MtaMessage> inboundMessages) { Parallel.ForEach(inboundMessages, message => { Enqueue(MtaQueuedMessage.CreateNew(message)).Wait(); }); RabbitMqManager.Ack(RabbitMqManager.RabbitMqQueue.Inbound, inboundMessages.Max(m => m.RabbitMqDeliveryTag), true); }
/// <summary> /// Handles a service unavailable event, should be same as defer but only wait 1 minute before next retry. /// </summary> /// <param name="sndIpAddress"></param> internal static async Task <bool> HandleServiceUnavailableAsync(MtaQueuedMessage msg, VirtualMTA ipAddress) { // Log deferral await MtaTransaction.LogTransactionAsync(msg, TransactionStatus.Deferred, "Service Unavailable", ipAddress, null); // Set next retry time and release the lock. msg.AttemptSendAfterUtc = DateTime.UtcNow.AddSeconds(15); await Requeue(msg); return(true); }
/// <summary> /// This method handles message throttle. /// Logs throttle /// Sets the next rety date time /// </summary> internal static async Task <bool> HandleDeliveryThrottleAsync(MtaQueuedMessage msg, VirtualMTA ipAddress, MXRecord mxRecord) { // Log deferral await MtaTransaction.LogTransactionAsync(msg, TransactionStatus.Throttled, string.Empty, ipAddress, mxRecord); // Set next retry time and release the lock. msg.AttemptSendAfterUtc = DateTime.UtcNow.AddMinutes(1); await Requeue(msg); return(true); }
/// <summary> /// /// </summary> /// <param name="ipAddress"></param> /// <param name="mxRecord"></param> /// <returns></returns> public static async Task <bool> HandleFailedToConnectAsync(MtaQueuedMessage msg, VirtualMTA ipAddress, MXRecord mxRecord) { // If there was no MX record in DNS, so using A, we should fail and not retry. if (mxRecord.MxRecordSrc == MxRecordSrc.A) { return(await HandleDeliveryFailAsync(msg, "550 Failed to connect", ipAddress, mxRecord)); } else { return(await HandleDeliveryDeferralAsync(msg, "Failed to connect", ipAddress, mxRecord, false, 15)); } }
/// <summary> /// Enqueues the Email that we are going to relay in RabbitMQ. /// </summary> /// <param name="messageID">ID of the Message being Queued.</param> /// <param name="ipGroupID">ID of the Virtual MTA Group to send the Message through.</param> /// <param name="internalSendID">ID of the Send the Message is apart of.</param> /// <param name="mailFrom">The envelope mailfrom, should be return-path in most instances.</param> /// <param name="rcptTo">The envelope rcpt to.</param> /// <param name="message">The Email.</param> /// <returns>True if the Email has been enqueued in RabbitMQ.</returns> public static bool Enqueue(Guid messageID, int ipGroupID, int internalSendID, string mailFrom, string[] rcptTo, string message) { // Create the thing we are going to queue in RabbitMQ. MtaMessage recordToSave = new MtaMessage(messageID, ipGroupID, internalSendID, mailFrom, rcptTo, message); return(RabbitMqManager.Publish(MtaQueuedMessage.CreateNew(recordToSave), RabbitMqManager.RabbitMqQueue.InboundStaging, true)); }
/// <summary> /// Dequeue a message from RabbitMQ. /// </summary> /// <returns>A dequeued message or null if there weren't any.</returns> public static async Task <MtaQueuedMessage> Dequeue() { BasicDeliverEventArgs ea = RabbitMqManager.Dequeue(RabbitMqManager.RabbitMqQueue.OutboundWaiting, 1, 100).FirstOrDefault(); if (ea == null) { return(null); } MtaQueuedMessage qmsg = await Serialisation.Deserialise <MtaQueuedMessage>(ea.Body); qmsg.RabbitMqDeliveryTag = ea.DeliveryTag; qmsg.IsHandled = false; return(qmsg); }
/// <summary> /// Enqueues the Email that we are going to relay in RabbitMQ. /// </summary> /// <param name="messageID">ID of the Message being Queued.</param> /// <param name="ipGroupID">ID of the Virtual MTA Group to send the Message through.</param> /// <param name="internalSendID">ID of the Send the Message is apart of.</param> /// <param name="mailFrom">The envelope mailfrom, should be return-path in most instances.</param> /// <param name="rcptTo">The envelope rcpt to.</param> /// <param name="message">The Email.</param> /// <param name="priority">Priority of message.</param> /// <returns>True if the Email has been enqueued in RabbitMQ.</returns> public static async Task <bool> Enqueue(Guid messageID, int ipGroupID, int internalSendID, string mailFrom, string[] rcptTo, string message, RabbitMqPriority priority) { // Create the thing we are going to queue in RabbitMQ. var recordToSave = new MtaMessage { ID = messageID, InternalSendID = internalSendID, MailFrom = mailFrom, Message = message, RcptTo = rcptTo, VirtualMTAGroupID = ipGroupID, RabbitMqPriority = priority }; return(await RabbitMqManager.Publish(MtaQueuedMessage.CreateNew(recordToSave), RabbitMqManager.RabbitMqQueue.InboundStaging, true, priority)); }
/// <summary> /// This method handles message deferal. /// Logs deferral /// Fails the message if timed out /// or /// Sets the next rety date time /// </summary> /// <param name="defMsg">The deferal message from the SMTP server.</param> /// <param name="ipAddress">IP Address that send was attempted from.</param> /// <param name="mxRecord">MX Record of the server tried to send too.</param> /// <param name="isServiceUnavailable">If false will backoff the retry, if true will use the MtaParameters.MtaRetryInterval, /// this is needed to reduce the tail when sending as a message could get multiple try again laters and soon be 1h+ before next retry.</param> public static async Task <bool> HandleDeliveryDeferralAsync(MtaQueuedMessage msg, string defMsg, VirtualMTA ipAddress, MXRecord mxRecord, bool isServiceUnavailable = false, int?overrideTimeminutes = null) { // Log the deferral. await MtaTransaction.LogTransactionAsync(msg, TransactionStatus.Deferred, defMsg, ipAddress, mxRecord); // This holds the maximum interval between send retries. Should be put in the database. int maxInterval = 3 * 60; // Increase the defered count as the queued messages has been deferred. msg.DeferredCount++; // Hold the minutes to wait until next retry. double nextRetryInterval = MtaParameters.MtaRetryInterval; if (overrideTimeminutes.HasValue) { nextRetryInterval = overrideTimeminutes.Value; } else { if (!isServiceUnavailable) { // Increase the deferred wait interval by doubling for each retry. for (int i = 1; i < msg.DeferredCount; i++) { nextRetryInterval = nextRetryInterval * 2; } // If we have gone over the max interval then set to the max interval value. if (nextRetryInterval > maxInterval) { nextRetryInterval = maxInterval; } } else { nextRetryInterval = 1; // For service unavalible use 1 minute between retries. } } // Set next retry time and release the lock. msg.AttemptSendAfterUtc = DateTime.UtcNow.AddMinutes(nextRetryInterval); await Requeue(msg); return(true); }
/// <summary> /// This method handles failure of delivery. /// Logs failure /// Deletes queued data /// </summary> /// <param name="failMsg"></param> public static async Task <bool> HandleDeliveryFailAsync(MtaQueuedMessage msg, string failMsg, VirtualMTA ipAddress, MXRecord mxRecord) { await MtaTransaction.LogTransactionAsync(msg, TransactionStatus.Failed, failMsg, ipAddress, mxRecord); try { // Send fails to Manta.Core.Events for (int i = 0; i < msg.RcptTo.Length; i++) { EmailProcessingDetails processingInfo = null; EventsManager.Instance.ProcessSmtpResponseMessage(failMsg, msg.RcptTo[i], msg.InternalSendID, out processingInfo); } } catch (Exception) { } msg.IsHandled = true; return(true); }
private static async Task HandleDequeue() { if (_StartedThreads >= STAGING_DEQUEUE_THREADS) { return; } _StartedThreads++; while (true) { BasicDeliverEventArgs ea = RabbitMq.RabbitMqManager.Dequeue(RabbitMqManager.RabbitMqQueue.InboundStaging, 1, 100).FirstOrDefault(); if (ea == null) { await Task.Delay(1000); continue; } MtaQueuedMessage qmsg = await Serialisation.Deserialise <MtaQueuedMessage>(ea.Body); MtaMessage msg = new MtaMessage { ID = qmsg.ID, InternalSendID = qmsg.InternalSendID, MailFrom = qmsg.MailFrom, RcptTo = qmsg.RcptTo, VirtualMTAGroupID = qmsg.VirtualMTAGroupID }; await RabbitMqManager.Publish(msg, RabbitMqManager.RabbitMqQueue.Inbound, true, (RabbitMqPriority)qmsg.RabbitMqPriority); await RabbitMqManager.Publish(qmsg, RabbitMqManager.RabbitMqQueue.OutboundWaiting, true, (RabbitMqPriority)qmsg.RabbitMqPriority); RabbitMqManager.Ack(RabbitMqManager.RabbitMqQueue.InboundStaging, ea.DeliveryTag, false); } }
private async Task SendMessageAsync(MtaQueuedMessage msg) { // Check that the message next attempt after has passed. if (msg.AttemptSendAfterUtc > DateTime.UtcNow) { await RabbitMqOutboundQueueManager.Enqueue(msg); await Task.Delay(50); // To prevent a tight loop within a Task thread we should sleep here. return; } if (await Data.MtaTransaction.HasBeenHandledAsync(msg.ID)) { msg.IsHandled = true; return; } // Get the send that this message belongs to so that we can check the send state. var snd = await SendManager.Instance.GetSendAsync(msg.InternalSendID); switch (snd.SendStatus) { // The send is being discarded so we should discard the message. case SendStatus.Discard: await MtaMessageHelper.HandleMessageDiscardAsync(msg); return; // The send is paused, the handle pause state will delay, without deferring, the message for a while so we can move on to other messages. case SendStatus.Paused: await MtaMessageHelper.HandleSendPaused(msg); return; // Send is active so we don't need to do anything. case SendStatus.Active: break; // Unknown send state, requeue the message and log error. Cannot send! default: msg.AttemptSendAfterUtc = DateTime.UtcNow.AddMinutes(1); await RabbitMqOutboundQueueManager.Enqueue(msg); Logging.Error("Failed to send message. Unknown SendStatus[" + snd.SendStatus + "]!"); return; } // Check the message hasn't timed out. If it has don't attempt to send it. // Need to do this here as there may be a massive backlog on the server // causing messages to be waiting for ages after there AttemptSendAfter // before picking up. The MAX_TIME_IN_QUEUE should always be enforced. if (msg.AttemptSendAfterUtc - msg.QueuedTimestampUtc > new TimeSpan(0, MtaParameters.MtaMaxTimeInQueue, 0)) { await MtaMessageHelper.HandleDeliveryFailAsync(msg, MtaParameters.TIMED_OUT_IN_QUEUE_MESSAGE, null, null); } else { MailAddress rcptTo = new MailAddress(msg.RcptTo[0]); MailAddress mailFrom = new MailAddress(msg.MailFrom); MXRecord[] mXRecords = DNSManager.GetMXRecords(rcptTo.Host); // If mxs is null then there are no MX records. if (mXRecords == null || mXRecords.Length < 1) { await MtaMessageHelper.HandleDeliveryFailAsync(msg, "550 Domain Not Found.", null, null); } else if (IsMxBlacklisted(mXRecords)) { await MtaMessageHelper.HandleDeliveryFailAsync(msg, "550 Domain blacklisted.", null, mXRecords[0]); } else { var vMtaGroup = VirtualMtaManager.GetVirtualMtaGroup(msg.VirtualMTAGroupID); var sendResult = await MantaSmtpClientPoolCollection.Instance.SendAsync(mailFrom, rcptTo, vMtaGroup, mXRecords, msg.Message); switch (sendResult.MantaOutboundClientResult) { case MantaOutboundClientResult.FailedToConnect: await MtaMessageHelper.HandleFailedToConnectAsync(msg, sendResult.VirtualMTA, sendResult.MXRecord); break; case MantaOutboundClientResult.MaxConnections: await RabbitMqOutboundQueueManager.Enqueue(msg); break; case MantaOutboundClientResult.MaxMessages: await MtaMessageHelper.HandleDeliveryThrottleAsync(msg, sendResult.VirtualMTA, sendResult.MXRecord); break; case MantaOutboundClientResult.RejectedByRemoteServer: if (string.IsNullOrWhiteSpace(sendResult.Message)) { Logging.Error("RejectedByRemoteServer but no message!"); await MtaMessageHelper.HandleDeliveryDeferralAsync(msg, sendResult.Message, sendResult.VirtualMTA, sendResult.MXRecord); } if (sendResult.Message[0] == '4') { await MtaMessageHelper.HandleDeliveryDeferralAsync(msg, sendResult.Message, sendResult.VirtualMTA, sendResult.MXRecord); } else { await MtaMessageHelper.HandleDeliveryFailAsync(msg, sendResult.Message, sendResult.VirtualMTA, sendResult.MXRecord); } break; case MantaOutboundClientResult.ServiceNotAvalible: await MtaMessageHelper.HandleServiceUnavailableAsync(msg, sendResult.VirtualMTA); break; case MantaOutboundClientResult.Success: await MtaMessageHelper.HandleDeliverySuccessAsync(msg, sendResult.VirtualMTA, sendResult.MXRecord, sendResult.Message); break; default: // Something weird happening with this message, get it out of the way for a bit. msg.AttemptSendAfterUtc = DateTime.UtcNow.AddMinutes(5); await RabbitMqOutboundQueueManager.Enqueue(msg); break; } } } }
/// <summary> /// This method handles message deferal. /// Logs deferral /// Fails the message if timed out /// or /// Sets the next rety date time /// </summary> /// <param name="defMsg">The deferal message from the SMTP server.</param> /// <param name="ipAddress">IP Address that send was attempted from.</param> /// <param name="mxRecord">MX Record of the server tried to send too.</param> /// <param name="isServiceUnavailable">If false will backoff the retry, if true will use the MtaParameters.MtaRetryInterval, /// this is needed to reduce the tail when sending as a message could get multiple try again laters and soon be 1h+ before next retry.</param> public static void HandleDeliveryDeferral(MtaQueuedMessage msg, string defMsg, VirtualMTA ipAddress, MXRecord mxRecord, bool isServiceUnavailable = false) { HandleDeliveryDeferralAsync(msg, defMsg, ipAddress, mxRecord, isServiceUnavailable).Wait(); }
/// <summary> /// Requeue the message in RabbitMQ. /// </summary> private static async Task Requeue(MtaQueuedMessage msg) { await RabbitMq.RabbitMqOutboundQueueManager.Enqueue(msg); msg.IsHandled = true; }
public void Start() { Thread t = new Thread(new ThreadStart(() => { // Dictionary will hold a single int for each running task. The int means nothing. ConcurrentDictionary <Guid, int> runningTasks = new ConcurrentDictionary <Guid, int>(); Action <MtaQueuedMessage> taskWorker = (qMsg) => { // Generate a unique ID for this task. Guid taskID = Guid.NewGuid(); // Add this task to the running list. if (!runningTasks.TryAdd(taskID, 1)) { return; } Task.Run(async() => { try { // Loop while there is a task message to send. while (qMsg != null && !_IsStopping) { // Send the message. await SendMessageAsync(qMsg); if (!qMsg.IsHandled) { Logging.Warn("Message not handled " + qMsg.ID); qMsg.AttemptSendAfterUtc = DateTime.UtcNow.AddMinutes(1); RabbitMq.RabbitMqOutboundQueueManager.Enqueue(qMsg); } // Acknowledge of the message. RabbitMqOutboundQueueManager.Ack(qMsg); // Try to get another message to send. qMsg = RabbitMq.RabbitMqOutboundQueueManager.Dequeue(); } } catch (Exception ex) { // Log if we can't send the message. Logging.Debug("Failed to send message", ex); } finally { // If there is still a acknowledge of the message. if (qMsg != null) { if (!qMsg.IsHandled) { Logging.Warn("Message not handled " + qMsg.ID); qMsg.AttemptSendAfterUtc = DateTime.UtcNow.AddMinutes(1); RabbitMq.RabbitMqOutboundQueueManager.Enqueue(qMsg); } RabbitMqOutboundQueueManager.Ack(qMsg); } // Remove this task from the dictionary int value; runningTasks.TryRemove(taskID, out value); } }); }; Action startWorkerTasks = () => { while ((runningTasks.Count < MAX_SENDING_WORKER_TASKS) && !_IsStopping) { MtaQueuedMessage qmsg = RabbitMq.RabbitMqOutboundQueueManager.Dequeue(); if (qmsg == null) { break; // Nothing to do, so don't start anymore workers. } taskWorker(qmsg); } }; while (!_IsStopping) { if (runningTasks.Count >= MAX_SENDING_WORKER_TASKS) { Thread.Sleep(100); continue; } startWorkerTasks(); } })); t.Start(); }
/// <summary> /// Handle the message for a paused send. /// Should increase attempt send after timestamp and requeue in RabbitMQ. /// </summary> internal static async Task HandleSendPaused(MtaQueuedMessage msg) { msg.AttemptSendAfterUtc = DateTime.UtcNow.AddMinutes(1); await Requeue(msg); }
private async Task <bool> SendMessageAsync(MtaQueuedMessage msg) { // Check that the message next attempt after has passed. if (msg.AttemptSendAfterUtc > DateTime.UtcNow) { RabbitMqOutboundQueueManager.Enqueue(msg); await Task.Delay(50); // To prevent a tight loop within a Task thread we should sleep here. return(false); } if (await MtaTransaction.HasBeenHandledAsync(msg.ID)) { msg.IsHandled = true; return(true); } // Get the send that this message belongs to so that we can check the send state. Send snd = await SendManager.Instance.GetSendAsync(msg.InternalSendID); switch (snd.SendStatus) { // The send is being discarded so we should discard the message. case SendStatus.Discard: await msg.HandleMessageDiscardAsync(); return(false); // The send is paused, the handle pause state will delay, without deferring, the message for a while so we can move on to other messages. case SendStatus.Paused: msg.HandleSendPaused(); return(false); // Send is active so we don't need to do anything. case SendStatus.Active: break; // Unknown send state, requeue the message and log error. Cannot send! default: msg.AttemptSendAfterUtc = DateTime.UtcNow.AddMinutes(1); RabbitMqOutboundQueueManager.Enqueue(msg); Logging.Error("Failed to send message. Unknown SendStatus[" + snd.SendStatus + "]!"); return(false); } bool result; // Check the message hasn't timed out. If it has don't attempt to send it. // Need to do this here as there may be a massive backlog on the server // causing messages to be waiting for ages after there AttemptSendAfter // before picking up. The MAX_TIME_IN_QUEUE should always be enforced. if (msg.AttemptSendAfterUtc - msg.QueuedTimestampUtc > new TimeSpan(0, MtaParameters.MtaMaxTimeInQueue, 0)) { await msg.HandleDeliveryFailAsync(MtaParameters.TIMED_OUT_IN_QUEUE_MESSAGE, null, null); result = false; } else { MailAddress mailAddress = new MailAddress(msg.RcptTo[0]); MailAddress mailFrom = new MailAddress(msg.MailFrom); MXRecord[] mXRecords = DNSManager.GetMXRecords(mailAddress.Host); // If mxs is null then there are no MX records. if (mXRecords == null || mXRecords.Length < 1) { await msg.HandleDeliveryFailAsync("550 Domain Not Found.", null, null); result = false; } else if (IsMxBlacklisted(mXRecords)) { await msg.HandleDeliveryFailAsync("550 Domain blacklisted.", null, mXRecords[0]); result = false; } else { // The IP group that will be used to send the queued message. VirtualMtaGroup virtualMtaGroup = VirtualMtaManager.GetVirtualMtaGroup(msg.VirtualMTAGroupID); VirtualMTA sndIpAddress = virtualMtaGroup.GetVirtualMtasForSending(mXRecords[0]); SmtpOutboundClientDequeueResponse dequeueResponse = await SmtpClientPool.Instance.DequeueAsync(sndIpAddress, mXRecords); switch (dequeueResponse.DequeueResult) { case SmtpOutboundClientDequeueAsyncResult.Success: case SmtpOutboundClientDequeueAsyncResult.NoMxRecords: case SmtpOutboundClientDequeueAsyncResult.FailedToAddToSmtpClientQueue: case SmtpOutboundClientDequeueAsyncResult.Unknown: break; // Don't need to do anything for these results. case SmtpOutboundClientDequeueAsyncResult.FailedToConnect: await msg.HandleFailedToConnectAsync(sndIpAddress, mXRecords[0]); break; case SmtpOutboundClientDequeueAsyncResult.ServiceUnavalible: await msg.HandleServiceUnavailableAsync(sndIpAddress); break; case SmtpOutboundClientDequeueAsyncResult.Throttled: await msg.HandleDeliveryThrottleAsync(sndIpAddress, mXRecords[0]); break; case SmtpOutboundClientDequeueAsyncResult.FailedMaxConnections: msg.AttemptSendAfterUtc = DateTime.UtcNow.AddSeconds(2); RabbitMqOutboundQueueManager.Enqueue(msg); break; } SmtpOutboundClient smtpClient = dequeueResponse.SmtpOutboundClient; // If no client was dequeued then we can't currently send. // This is most likely a max connection issue. Return false but don't // log any deferal or throttle. if (smtpClient == null) { result = false; } else { try { Action <string> failedCallback = delegate(string smtpResponse) { // If smtpRespose starts with 5 then perm error should cause fail if (smtpResponse.StartsWith("5")) { msg.HandleDeliveryFailAsync(smtpResponse, sndIpAddress, smtpClient.MXRecord).Wait(); } else { // If the MX is actively denying use service access, SMTP code 421 then we should inform // the ServiceNotAvailableManager manager so it limits our attepts to this MX to 1/minute. if (smtpResponse.StartsWith("421")) { ServiceNotAvailableManager.Add(smtpClient.SmtpStream.LocalAddress.ToString(), smtpClient.MXRecord.Host, DateTime.UtcNow); msg.HandleDeliveryDeferral(smtpResponse, sndIpAddress, smtpClient.MXRecord, true); } else { // Otherwise message is deferred msg.HandleDeliveryDeferral(smtpResponse, sndIpAddress, smtpClient.MXRecord, false); } } throw new SmtpTransactionFailedException(); }; // Run each SMTP command after the last. await smtpClient.ExecHeloOrRsetAsync(failedCallback); await smtpClient.ExecMailFromAsync(mailFrom, failedCallback); await smtpClient.ExecRcptToAsync(mailAddress, failedCallback); await smtpClient.ExecDataAsync(msg.Message, failedCallback, async (response) => { await msg.HandleDeliverySuccessAsync(sndIpAddress, smtpClient.MXRecord, response); }); SmtpClientPool.Instance.Enqueue(smtpClient); result = true; } catch (SmtpTransactionFailedException) { // Exception is thrown to exit transaction, logging of deferrals/failers already handled. result = false; } catch (Exception ex) { Logging.Error("MessageSender error.", ex); if (msg != null) { msg.HandleDeliveryDeferral("Connection was established but ended abruptly.", sndIpAddress, smtpClient.MXRecord, false); } result = false; } finally { if (smtpClient != null) { smtpClient.IsActive = false; smtpClient.LastActive = DateTime.UtcNow; } } } } } return(result); }
/// <summary> /// Acknowledge the message as handled. /// </summary> /// <param name="msg">The message to acknowledge.</param> internal static void Ack(MtaQueuedMessage msg) { RabbitMqManager.Ack(RabbitMqManager.RabbitMqQueue.OutboundWaiting, msg.RabbitMqDeliveryTag, false); }