public async Task <IActionResult> Post( [FromForm] PostEmailRequest args, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var applicationId = User.GetApplicationId(); // the token will be used by both the processing engine and the // client to track this request from start to finish var token = EmailQueueToken.Create(applicationId); _logger.LogInformation("Sending email using token {0}", token); if (ModelState.IsValid) { // create an object that we then store as a BLOB (emails run // the risk of being too large to fit in the queue, so BLOB // storage is the best option) var message = BuildMessage(args); message.ApplicationId = applicationId; await _blobStore.AddAsync(token, message, cancellationToken); // now we can let the back-end processor know that there's a // new message that it has to process await _sender.SendAsync(token, cancellationToken); // log that we queued the message for processing await _logWriter.TryLogProcessAttemptAsync(token, 0, ProcessingStatus.Pending, token.TimeStamp, token.TimeStamp, null, cancellationToken); // all done - let the client know that we've accepted their // request, and what the tracking token is Response.StatusCode = (int)HttpStatusCode.Accepted; return(Json(new { Token = token.EncodeString() })); } else { return(BadRequest(ModelState)); } }
public async Task ProcessMessage(TMessage message, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); _logger.LogTrace("Processing message {0} on try {1}", message.Token, message.DequeueCount); // start timing the process for logging purposes var start = DateTime.UtcNow; var sw = new Stopwatch(); sw.Start(); // load the args from the blob store; it's unlikely that they won't be found, // but to be safe we'll check for null (the args are in the blob store because // there's a chance that they'll exceed the 64KB limit on queue messages due // to including data and HTML) var args = await _blobStore.GetAsync(message.Token, cancellationToken); if (args != null) { try { // do the actual sending and return information about the email that // was sent so that we can log it; any failures will result in an // exception that will be caught and handled in the catch block var result = await TrySendEmailAsync(args); result.DequeueCount = message.DequeueCount; // after successful processing, the most important thing to do is // to immediately remove this message from the queue; this will prevent // retrieves if any of the subsequent calls fail (avoiding duplicate // messages is more important than correct logging) await _receiver.CompleteAsync(message, cancellationToken); await _blobStore.RemoveAsync(message.Token, cancellationToken); // stop timing the process sw.Stop(); // now we can audit the event await _logWriter.TryLogProcessAttemptAsync(message.Token, message.DequeueCount, ProcessingStatus.Succeeded, start, start.Add(sw.Elapsed), null, cancellationToken); await _logWriter.TryLogSentMessageAsync(message.Token, result, cancellationToken); _logger.LogInformation("Successfully sent email for message {0} on try {1}", message.Token, message.DequeueCount); } catch (Exception ex) { sw.Stop(); if (message.DequeueCount >= MaxDequeue) { // failed, and reached the maximum retry count; move the message to // the poison queue and move the blob data to the bin of failure await _receiver.MoveToPoisonQueueAsync(message, cancellationToken); await _blobStore.MoveToPoisonStoreAsync(message.Token, cancellationToken); await _logWriter.TryLogProcessAttemptAsync(message.Token, message.DequeueCount, ProcessingStatus.FailedAbandoned, start, start.Add(sw.Elapsed), ex.GetBaseException().Message, cancellationToken); _logger.LogError("Failed to send email for message {0} after {1} tries, giving up\n{2}", message.Token, message.DequeueCount, ex); } else { // failed, but we'll let it retry to see if that fixes it await _logWriter.TryLogProcessAttemptAsync(message.Token, message.DequeueCount, ProcessingStatus.FailedRequeued, start, start.Add(sw.Elapsed), ex.GetBaseException().Message, cancellationToken); _logger.LogWarning("Failed to send email for message {0} after {1} tries\n{2}", message.Token, message.DequeueCount, ex); } } } else { // if we couldn't find a matching blob, then raise an error and remove // the message from the queue _logger.LogError("Could not find message params for {0}", message.Token); await _receiver.CompleteAsync(message, cancellationToken); } }