/// <summary>
        /// The <see cref="ProcessMessageStreamAsync(Stream)"/> method is overridden to
        /// provide specific behavior for this base class.
        /// </summary>
        /// <remarks>
        /// This method cannot be overridden.
        /// </remarks>
        /// <param name="stream">The stream with the request payload.</param>
        /// <returns>The task object representing the asynchronous operation.</returns>
        public override sealed async Task <Stream> ProcessMessageStreamAsync(Stream stream)
        {
            // read stream into memory
            LogInfo("reading stream body");
            string snsEventBody;

            using (var reader = new StreamReader(stream)) {
                snsEventBody = reader.ReadToEnd();
            }
            var stopwatch = Stopwatch.StartNew();
            var metrics   = new List <LambdaMetric>();

            // process received sns record (there is only ever one)
            try {
                // sns event deserialization
                LogInfo("deserializing SNS event");
                try {
                    var snsEvent = LambdaSerializerSettings.LambdaSharpSerializer.Deserialize <SNSEvent>(snsEventBody);
                    _currentRecord = snsEvent.Records.First().Sns;

                    // message deserialization
                    LogInfo("deserializing message");
                    var message = Deserialize(CurrentRecord.Message);

                    // process message
                    LogInfo("processing message");
                    await ProcessMessageAsync(message);

                    // record successful processing metrics
                    stopwatch.Stop();
                    var now = DateTimeOffset.UtcNow;
                    metrics.Add(("MessageSuccess.Count", 1, LambdaMetricUnit.Count));
                    metrics.Add(("MessageSuccess.Latency", stopwatch.Elapsed.TotalMilliseconds, LambdaMetricUnit.Milliseconds));
                    metrics.Add(("MessageSuccess.Lifespan", (now - CurrentRecord.GetLifespanTimestamp()).TotalSeconds, LambdaMetricUnit.Seconds));
                    return("Ok".ToStream());
                } catch (Exception e) {
                    LogError(e);
                    try {
                        // attempt to send failed message to the dead-letter queue
                        await RecordFailedMessageAsync(LambdaLogLevel.ERROR, FailedMessageOrigin.SNS, LambdaSerializer.Serialize(snsEventBody), e);

                        // record failed processing metrics
                        metrics.Add(("MessageDead.Count", 1, LambdaMetricUnit.Count));
                    } catch {
                        // NOTE (2020-04-22, bjorg): since the message could not be sent to the dead-letter queue,
                        //  the next best action is to let Lambda retry it; unfortunately, there is no way
                        //  of knowing how many attempts have occurred already.

                        // unable to forward message to dead-letter queue; report failure to lambda so it can retry
                        metrics.Add(("MessageFailed.Count", 1, LambdaMetricUnit.Count));
                        throw;
                    }
                    return($"ERROR: {e.Message}".ToStream());
                }
            } finally {
                _currentRecord = null;
                LogMetric(metrics);
            }
        }