Backend engineer testing AWS Lambda and Amazon SQS partial batch failure handling at a workstation

Design Lambda–SQS Retries Around the Records That Failed

Fri, Sep 18, 2026

Design Lambda–SQS Retries Around the Records That Failed

A four-record batch is enough to expose the reliability problem. In a synthetic test, record A can complete its business effect, record B can fail validation before any effect, record C can commit an external operation but lose the response, and record D can terminate the invocation before the handler returns. The interesting question is not whether the Lambda invocation is green or red. It is: what is known about each record, what business effect may already exist, and what action is safe next?

Amazon Simple Queue Service (SQS) event source mappings are explicitly an at-least-once mechanism: Lambda polls the queue, invokes the function synchronously with a batch, and duplicate processing can occur. AWS therefore recommends idempotent processing. A successful batch lets Lambda delete its messages; a failed batch normally becomes visible again after the visibility timeout. AWS, Using Lambda with Amazon SQS, living documentation; publication date not asserted; accessed September 17, 2026.

This playbook uses a synthetic standard-queue lab in the AWS commercial partition with the supported nodejs24.x Lambda runtime pinned for the fixture. AWS lists Node.js 24 as supported; preview runtimes are deliberately excluded from the main lab. AWS, Lambda runtimes, living documentation; publication date not asserted; research cutoff September 17, 2026. The tests below are proposed unless explicitly described as documented service behavior; no cloud execution result is claimed.

Define the record-level recovery contract

The operating invariant is simple: every delivered record must have a business identity, an observed outcome, and an approved next action. That is a stronger review standard than “the handler returned successfully,” because the Lambda response settles only the event-source mapping’s acknowledgement decision. It does not atomically commit or roll back a database write, an entitlement grant, an email send, or an external API call.

This is also why the scope stays narrow. The mechanism here is a Lambda SQS event source mapping, not a Lambda asynchronous invocation destination, a Kinesis checkpoint, an SNS delivery policy, or a direct API Gateway request. Those mechanisms have different acknowledgement and retry contracts. General serverless API architecture is useful context, but the recovery decision here belongs at the record/effect boundary, not at the provider-overview level.

Boundary

What the platform or application knows

What it does not prove

Recovery question

SQS delivery

A message was received with transport metadata

That this is the first delivery

Is this business operation already known?

Lambda invocation

A batch entered one handler invocation

That every record reached the same processing stage

Which records started, finished, or were interrupted?

Application processing

Validation and business logic reached recorded states

That an external side effect committed

What durable evidence exists?

External effect

Destination may confirm, reject, or leave the result ambiguous

That Lambda will acknowledge the record

May the operation safely be issued again?

The contract deliberately produces three decisions rather than one: go when repeating work is proven safe or unnecessary, hold when evidence is missing, and reconcile when an authoritative destination or business owner must resolve ambiguity. “Retryable” is therefore a business conclusion backed by evidence, not a synonym for “the function threw.”

Inventory the mapping before changing the handler

Before writing partial-batch code, capture the deployed system. Source control can tell you what somebody intended; recovery depends on what Lambda and SQS are actually configured to do. The inventory should be timestamped and attached to the change or incident record so reviewers can later explain why a message was retried, deleted, or dead-lettered.

At minimum, record the event-source mapping UUID and source Amazon Resource Name (ARN), queue type, Lambda runtime and deployed code revision, enabled function response types, batch size and batching window, function timeout, queue visibility timeout, source/DLQ retention, redrive maxReceiveCount, reserved or mapping concurrency controls in use, and the identity of the deployment artifact. The exact concurrency design is workload-specific; the point is to record it because throttling and capacity limits alter the retry timeline.

Inventory field

Example fixture value

Why recovery needs it

Mapping UUID

esm-EXAMPLE-001

Identifies the mapping whose response contract is active

Source

synthetic-orders-standard

Distinguishes standard from FIFO behavior

Runtime

nodejs24.x

Pins the executable fixture

Function response types

ReportBatchItemFailures or empty

Determines whether record failures can be reported

Batch settings

size 4, window 2s

Bounds what can share one invocation

Timeout / visibility

example only: 20s / 122s

Supports visibility review; values are not universal

Redrive

synthetic DLQ, example maxReceiveCount=5

Explains when poison records leave the source queue

Code revision

immutable deployment ID

Connects behavior to code actually running

AWS documents the event-source setting as FunctionResponseTypes and the partial-batch value as ReportBatchItemFailures; AWS also exposes the mapping configuration for read-back. The broader documentation point is the same as with event-driven API contracts: a schema describes a message, but it does not by itself establish acknowledgement, retry, or business-effect semantics.

Separate transport identity from business identity

Do not use “message ID” as shorthand for every identity in the system. SQS assigns a messageId to a message. A receipt handle belongs to a receive action, and AWS documents that a newly received copy gets a different receipt handle; the latest receipt handle is needed for deletion. AWS, Amazon SQS queue and message identifiers, living documentation; publication date not asserted; accessed September 17, 2026.

Record four identifiers separately where they exist: SQS messageId; current receipt handle, retained only where operationally appropriate; a producer event identifier inside the payload; and a domain operation key such as entitlement:account-41:feature-alpha:v3. The domain key is what lets the application recognize the same intended business operation if a producer sends a second SQS message with a different transport identifier.

Give every record an evidence state

Use a proposed durable ledger rather than reconstructing history from log lines. A compact state model is enough:

State

Meaning

Permitted next action

RECEIVED

Transport record observed

Start after identity and payload checks

STARTED

Business processing claimed

Resume only under concurrency rules

EFFECT_CONFIRMED

Authoritative evidence says effect exists

Do not reissue; acknowledge when possible

EFFECT_UNKNOWN

Request may have committed but confirmation was lost

Reconcile; never blindly repeat

FAILED_BEFORE_EFFECT

Failure occurred before side-effect boundary

Retry after cause is acceptable

RESPONSE_EMITTED

Handler included record in final response decision

Compare with mapping behavior

Assign an owner to EFFECT_UNKNOWN. An ambiguous record that nobody owns is not merely “pending”; it is a replay hazard.

Build a small synthetic batch with deliberate failure points

Use four synthetic records whose failure modes can be switched deterministically. The business operation is an invented “feature entitlement grant,” chosen because it has an observable destination state without involving customer data or money. The destination for unit tests is a local fake that can confirm a write, reject before writing, or persist a write and then close the connection before sending confirmation.

Record

Domain key

Injected behavior

Expected evidence before handler response

A

entitlement:41:alpha:v3

Commit and confirm

EFFECT_CONFIRMED

B

entitlement:42:beta:v3

Schema rule rejects before destination call

FAILED_BEFORE_EFFECT

C

entitlement:43:gamma:v3

Commit, then simulate lost response

EFFECT_UNKNOWN until lookup

D

entitlement:44:delta:v3

Invocation interruption switch

Evidence stops wherever interruption occurs

Prerequisites are deliberately bounded: an isolated non-production stack in a commercial AWS Region; a standard SQS source queue and DLQ containing only synthetic payloads; a Lambda function pinned to nodejs24.x; least-privilege execution permissions; a durable test effect ledger; and a fake or disposable destination whose state can be queried. Do not put credentials in messages, logs, test fixtures, or incident exports.

Run the fixture in two layers. First, use the local fake to prove application transitions. Assert that A writes once and reaches EFFECT_CONFIRMED; B makes zero destination calls and reaches FAILED_BEFORE_EFFECT; C writes once but reaches EFFECT_UNKNOWN; and a second attempt of C performs reconciliation before any second write. This verifies the application model only. It does not prove how the managed event-source mapping interprets a response.

Second, send equivalent synthetic records to the disposable AWS queue. With the interruption switch disabled, validate partial response behavior. With it enabled, terminate or time out the invocation in a separate test and observe which records reappear. Capture message IDs, approximate receive counts where available, Lambda request IDs, ledger states, response payloads that were actually emitted, and queue observations. The expected assertion is not “no duplicates”; it is “every redelivery has a defensible next action.”

Cleanup is part of reproducibility. Disable the test mapping, export only the synthetic identifiers and state transitions needed for evidence, drain or purge only the disposable queues, delete the temporary queues/function/ledger, and remove the fake destination data. A mock passing locally is evidence about code paths; a disposable managed-service run is the evidence for mapping behavior.

Make each test repeatable from a manifest rather than from hand-edited messages. The manifest should pin the four payloads, the destination fault mode, the expected ledger transition, and whether interruption is enabled. Reset the fake destination and effect ledger between cases so record C’s prior commit cannot accidentally make a later run look idempotent. For the AWS layer, use a fresh fixture namespace or record the prior state explicitly. The assertion should compare the expected and observed sequence of evidence, not only the final queue location.

One useful negative control is to run A twice under two distinct SQS messages carrying the same domain key. The expected application assertion is one business effect and two observed transport identities. That test demonstrates why SQS message idempotency and business idempotency are different problems without depending on a rare duplicate delivery from the service itself.

Validate the partial-response contract at both ends

Partial reporting has two prerequisites: the event-source mapping must have ReportBatchItemFailures enabled, and the handler must return the documented response shape. AWS states that, by default, a batch error makes every message in the batch visible again. With ReportBatchItemFailures, Lambda can make only the reported failed messages visible again. A function-level exception is different: AWS treats the entire batch as failed. AWS, Handling errors for an SQS event source in Lambda, living documentation; publication date not asserted; accessed September 17, 2026.

A minimal unexecuted Node.js 24 handler for the response mechanics looks like this. It is intentionally not an idempotency implementation:

// Unexecuted fixture code: response mechanics only.
export const handler = async (event) => {
  const batchItemFailures = [];

  for (const record of event.Records) {
    try {
      await processRecord(record); // synthetic test implementation
    } catch (error) {
      batchItemFailures.push({ itemIdentifier: record.messageId });
    }
  }

  return { batchItemFailures };
};

The response validator should test documented cases separately. AWS treats an empty batchItemFailures list, a null list, an empty event response, or a null event response as complete success. It documents invalid JSON, empty or null itemIdentifier, a bad key name, and a non-existent message ID as complete failure. Do not substitute an HTTP status code; this invocation contract is not an HTTP API response.

Test input

Expected assertion

{"batchItemFailures":[]}

Documented complete success

{} with no batchItemFailures field

Documented complete success as an empty event response

Valid list containing B’s real messageId

With mapping enabled, B is the reported failed item

Malformed JSON

Documented complete failure

itemIdentifier:""

Documented complete failure

{ "batchItemFailures": [ {} ] } with missing itemIdentifier

Lab question; verify rather than infer an undocumented exact result

Unknown message ID

Documented complete failure

Handler throws after processing records

Documented whole-batch failure

Then read back the deployed mapping and prove ReportBatchItemFailures is active. A unit test that serializes the right JSON is necessary but insufficient: if the mapping setting is absent, the intended record-level acknowledgement contract is not active. Any response edge case not explicitly settled in the primary documentation should remain a lab question, not become a production assumption.

Make a repeated business operation safe or explicitly unresolved

SQS can redeliver; Lambda can retry; and a destination can commit an operation before the caller receives a response. Those are separate facts. The practical objective is therefore repeat-safe business execution, not a global exactly-once claim. AWS explicitly warns that SQS event-source mappings process events at least once and duplicates can occur.

Treat the business effect as its own state machine. A proposed ledger row can contain a domain operation key, payload fingerprint, state, current claim owner or lease, destination reference, confirmation timestamp, last SQS message ID, observed receive count, and reconciliation owner. The ledger must be durable enough to survive invocation failure and must support an atomic conditional claim; a cache lookup followed by an unprotected destination write leaves a race in which two workers can both observe “not done” and both perform the effect.

Ledger observation on delivery

Meaning

Handler behavior

No row

Operation not yet claimed

Atomically claim, then process

IN_PROGRESS owned by live worker

Concurrent work exists

Do not issue duplicate effect; defer according to policy

EFFECT_CONFIRMED

Destination effect is known

Skip effect and treat business work as complete

EFFECT_UNKNOWN

Commit status unresolved

Reconcile first; do not reissue

FAILED_BEFORE_EFFECT

Prior attempt never crossed effect boundary

Retry may be safe after cause review

Same key, different payload fingerprint

Conflicting intent

Hold and escalate; never silently overwrite

This complements guidance on reliable external API integration, but the record consumer needs stronger replay evidence: the queue acknowledgement and the destination commit are not one transaction.

Choose the idempotency scope before the storage tool

Define the domain key first. For the fixture, entitlement:43:gamma:v3 means “apply this specific entitlement version to this account,” independent of SQS delivery identity. Define what constitutes a conflicting replay: if the same key arrives with a different entitlement payload, the correct action is a conflict, not “already processed.” Define who owns concurrent execution, how abandoned claims expire, and how the owner is recovered after a process dies.

Retention is an application requirement. Keep the effect evidence for at least the period in which the business operation can credibly be retried, redriven, manually replayed, or corrected. That window may exceed queue visibility or message retention. No universal number is asserted here; choose it from the application’s replay and audit model, then test expiry behavior explicitly.

Reconcile a committed effect with a lost response

Record C is the critical test. The fake destination commits the entitlement and then simulates a connection reset before the client receives confirmation. The first attempt must persist EFFECT_UNKNOWN. On redelivery, the consumer must not call “grant” again. It first queries an authoritative destination using the business key or recorded destination reference.

The decision table is intentionally conservative:

Reconciliation result

Safe next action

Destination proves effect exists and matches payload

Mark EFFECT_CONFIRMED; acknowledge without reissuing

Destination proves effect does not exist

Reissue only if the business contract permits it

Destination cannot determine outcome

Keep EFFECT_UNKNOWN; hold/retry reconciliation or escalate

Destination shows conflicting effect

Stop automated replay and investigate

If the destination has no authoritative lookup and no reliable idempotency facility, that is a product constraint, not something Lambda’s batch response can solve. Preserve the uncertainty and route it to documented business escalation rather than relabeling “unknown” as “failed.”

Budget visibility and invocation time from measured work

Visibility must be designed from measured batch behavior, but two AWS statements must not be blurred together. AWS recommends setting the source queue visibility timeout to at least six times the function timeout. Separately, Lambda validates that the function timeout is less than or equal to the queue visibility timeout when the event-source mapping is created or updated. If a non-zero batch window is used, AWS recommends six times the function timeout plus MaximumBatchingWindowInSeconds. AWS, Creating and configuring an Amazon SQS event source mapping, living documentation; publication date not asserted; accessed September 17, 2026.

That distinction matters in reviews. “Configuration accepted” proves the enforced inequality, not that the queue has adequate retry headroom for throttling or long-tail work. Conversely, the six-times guidance is a recommendation, not a promise that every workload needs or succeeds with one universal setting.

Worksheet input

Evidence to collect

Decision use

Function timeout

Deployed configuration

Enforced visibility comparison

Batching window

Deployed mapping

Add to AWS-recommended visibility calculation when non-zero

Batch size

Deployed mapping and observed records/invocation

Relates per-record work to invocation duration

p50/p95/p99 batch duration

Controlled measurements

Finds long-tail execution risk

Dependency timeout/retry budget

Client configuration

Prevents nested retries consuming whole invocation

Throttling/capacity observations

Queue and Lambda telemetry

Tests whether retry headroom is adequate

For illustration only, a synthetic 20s function timeout with a 2s batching window gives 6 × 20 + 2 = 122s under AWS’s documented recommendation. That is neither a measured result nor a production default. Replace the numbers with measured work and the deployed batch window.

Also model what happens when work approaches visibility expiry. A message can become available for another receive while an earlier worker still has uncertain progress, which is another reason an atomic business claim matters. Set explicit stop conditions for rollout: unexpected duplicate effects, rapidly increasing oldest-message age, timeouts approaching the configured function limit, or evidence that dependency retries consume the safety margin. The service owner who controls queue and function configuration must be identified before the test, not during an incident.

Measure batches by shape as well as duration. Four fast records do not validate four records that each fan out to a slow dependency. Record payload class, destination latency, client retry count, and time remaining in the Lambda context when the final record starts. The purpose is not to invent a throughput target; it is to know whether the configured batch can finish with margin under realistic slowness. If the measured tail no longer fits the operating envelope, reduce work per invocation, adjust timeouts under change control, or pause expansion.

Give FIFO a separate ordered-failure path

Do not reuse the standard-queue loop unchanged for a first-in, first-out (FIFO) queue. AWS’s partial-batch guidance says that with FIFO, the function should stop processing after the first failure and return all failed and unprocessed messages in batchItemFailures to help preserve ordering. That changes both code shape and test assertions.

Use a synthetic message group G-7 containing records 01, 02, and 03. Record 01 succeeds. Record 02 fails before its effect. Record 03 is valid but must not be processed after the failure in 02. The expected response reports 02 and 03: one failed, one unprocessed.

FIFO record

Processing outcome

Response membership

G-7/01

Effect confirmed before failure

Not reported failed

G-7/02

First failure

Report in batchItemFailures

G-7/03

Deliberately unprocessed

Also report in batchItemFailures

The test must prove two things: 03 never crossed its business-effect boundary, and the response includes every remaining unprocessed record. A loop that keeps processing after 02 may be convenient for throughput, but it contradicts the conservative FIFO guidance used in this playbook.

Dead-letter handling also needs a FIFO-specific business decision. AWS warns against using a DLQ with FIFO when moving a failed message out would break the exact order of messages or operations. AWS, Using dead-letter queues in Amazon SQS, living documentation; publication date not asserted; accessed September 17, 2026. This warning does not mean every FIFO workload forbids DLQs. It means the application owner must state whether a gap in the domain sequence is tolerable before configuring one.

For a domain where sequence is mandatory, hold the group and repair the blocking record rather than letting later operations leapfrog it through a convenience replay route. For a domain where ordering is only local or advisory, document that narrower requirement. “FIFO queue” describes transport semantics; it does not, by itself, specify the business consequence of skipping an operation.

Test invocation-wide failure and backpressure

Partial batch reporting is not a shield against invocation failure. AWS documents that if the function throws an exception, the entire batch is a complete failure. It also documents different backoff behavior for function-code errors and throttling, while noting that when ReportBatchItemFailures is activated Lambda does not scale down message polling merely because function invocations fail. That makes queue-level evidence essential.

Run five separate synthetic experiments so one failure mode does not hide another:

Experiment

Injection

Evidence to preserve

Question to answer

Handler exception

Throw outside per-record catch

Ledger states, invocation error, returning records

Did whole-batch failure override partial intent?

Process termination

Exit after A, before return

Durable ledger only; no assumed response

Which effects may already exist?

Timeout

Delay beyond configured timeout

Last durable state and queue reappearance

Which records were interrupted?

Dependency outage

Fake destination rejects all calls

Failure classification and polling behavior

Are retries amplifying an outage?

Capacity saturation

Intentionally constrained lab capacity

Queue age/depth and throttling evidence

Is backpressure visible where expected?

Do not infer success from a lower Lambda error count. A handler can catch record failures and emit a syntactically successful invocation while the queue accumulates failed records. Conversely, an invocation error can coexist with a business effect that committed before the crash. Observe both the function and the queue, then join those signals to the effect ledger.

For A–D, the process-termination test is particularly valuable. If A reached EFFECT_CONFIRMED before D triggers termination, the invocation may fail as a whole and A may be delivered again. The correct second-attempt behavior is to recognize A’s business key and skip the already-confirmed operation. That is why the replay safety test is stronger than the transport response test.

During a dependency outage, avoid converting every retry into a fresh external write attempt. Records in EFFECT_UNKNOWN should perform reconciliation; known pre-effect failures can remain retryable; confirmed records should short-circuit. Stop the experiment when safety evidence degrades rather than “letting the queue prove it.” Capacity tests belong in disposable or tightly isolated environments with explicit quotas and blast-radius controls.

Recover dead-letter messages without blind replay

A dead-letter queue (DLQ) is evidence that normal processing exhausted the source queue’s redrive policy; it is not evidence that every business operation failed. SQS uses maxReceiveCount to decide when a repeatedly received message moves to the DLQ, and AWS recommends choosing it high enough to allow sufficient retries. AWS’s Lambda mapping guide separately recommends a value of at least five for this integration; that is guidance, not a universal production requirement.

Before any redrive, build an incident manifest. It should contain only the identifiers and state needed to make the replay decision, not a copy of every payload.

Manifest field

Required question

Domain operation key

What business action does this record represent?

SQS message ID / observed receive count

Which transport history was seen?

Effect state

Confirmed, unknown, or failed before effect?

Failure cause

Has the trigger for failure been mitigated?

Retention runway

How long until evidence/message expiry matters?

Destination readiness

Can the dependency safely accept work now?

Reconciliation evidence

Can ambiguous outcomes be resolved?

Approver / owner

Who accepts replay risk and watches stop conditions?

Start with a bounded cohort whose effects are understood. Compare domain outcomes, not merely queue depth. A replay that empties the DLQ but creates duplicate entitlements is an operational failure even if every Lambda invocation succeeds.

Classify the cohort before moving anything. “Confirmed” records normally need no business re-execution; “failed before effect” records can be candidates once the original cause is fixed; “unknown” records go through reconciliation first; and conflicting-key records stay on hold. This classification prevents the DLQ from becoming a second scheduling system where transport position silently overrides business state. It also makes poison message isolation useful: a record can remain isolated while the healthy population proceeds, without granting blanket permission to replay the isolated operation.

Preserve the evidence before changing retention

SQS retention semantics differ between standard and FIFO DLQs. For a standard queue, AWS says message expiration remains based on the original enqueue timestamp when the message moves to the DLQ; ApproximateAgeOfOldestMessage in the DLQ reflects when it moved there, not its original age. For FIFO, the enqueue timestamp resets on the move, and the age metric reflects time in the DLQ. AWS recommends making a standard DLQ’s retention longer than the source queue’s retention.

Therefore, “the oldest DLQ message is three days old” is not a complete processing history for a standard queue. Preserve the manifest and ledger evidence before a retention change or incident cleanup. Export synthetic identifiers in this lab; in production, apply data-minimization rules and never place secrets or customer payloads in public incident notes.

Approve redrive in small accountable cohorts

Treat redrive as a controlled change with explicit gates. Native SQS redrive is a queue recovery mechanism, not a record-level business-approval system. Where an exact reviewed subset is required, a separately controlled replay path may be easier to audit than assuming the native operation can express arbitrary per-record selection. Any such replay tool must preserve or reconstruct the domain key because a newly enqueued message can have a new transport identity.

Gate

Proposed criterion

Enter

Root cause mitigated; destination healthy; each record classified; approver named

Continue

No new duplicate effect; unknown-effect count does not grow; queue telemetry remains explainable

Stop

Any conflicting effect, new schema mismatch, unexplained identity loss, or unexpected queue-age growth

Complete

Cohort drained; every business outcome confirmed or assigned for reconciliation

Do not automate “DLQ not empty, therefore redrive all.” For each cohort, record start/end time, approved domain keys, code revision, mapping configuration, effect-ledger snapshot, and final disposition. Redrive ends when the business outcomes are settled, not merely when the source counter returns to zero.

Measure record outcomes as well as invocations

Invocation metrics tell you whether Lambda ran; they do not tell you whether a record’s business effect is confirmed. Add application counters whose names are explicitly yours, not AWS metric names. The denominator matters as much as the count: an “unknown effects” total without the number of started records is hard to interpret during a rollout.

Proposed application metric

Suggested denominator / use

records_received_total

Base count of delivered records

records_retryable_failure_total

Divide by records evaluated for a retry decision

effects_confirmed_total

Compare with operations started

effects_unknown_total

Divide by operations that crossed or may have crossed the effect boundary

records_replayed_total

Base for replay outcome review

outcomes_reconciled_total

Compare with records previously unknown

Keep these separate from AWS service metrics. AWS specifically points to SQS NumberOfMessagesDeleted and ApproximateAgeOfOldestMessage as useful signals when checking partial batch failure reporting. A sharp queue-age increase can be a reason to investigate, but no universal alert threshold is asserted here; baseline it against the workload and the rollout stage.

A compact dashboard should show four layers side by side: queue backlog/age; Lambda invocations, errors and duration; record outcome rates from the application; and effect-ledger state counts. The dashboard contract should say which time window and denominator each panel uses. That prevents an incident review from comparing a five-minute function error rate with a one-hour count of effect states and drawing a false causal conclusion.

The same principle extends broader API security and observability: useful correlation should not require logging secrets. For a privacy-safe example, derive an opaque correlation token from the domain operation key with a server-side keyed hash, then log the token, SQS message ID, Lambda request ID, ledger transition, and outcome class. Keep raw customer data out of routine telemetry.

Finally, alert on unknown state, not only exceptions. A rising effects_unknown_total with low Lambda errors is precisely the condition a conventional invocation dashboard can miss. The owner of that metric needs the authority and destination access to reconcile it; otherwise observability stops at detection.

Decide whether to proceed, hold or reconcile

Turn the lab into an acceptance decision. Every test should name its evidence, owner, and action. A passing JSON unit test is a prerequisite for partial reporting; it is not proof that business replay is safe.

Test

Required evidence

Owner

Decision

Response-shape tests

Documented success/failure cases pass

Function owner

Proceed to mapping test

Mapping activation

Deployed read-back shows ReportBatchItemFailures

Platform owner

Proceed only when verified

Known committed effect replay

Second delivery performs zero duplicate writes

Application owner

Go if confirmed

Lost-response case

Authoritative lookup settles C before reissue

Integration owner

Reconcile until settled

Invocation interruption

Ledger and queue explain every returning record

Reliability owner

Hold on unexplained state

FIFO first-failure case

Unprocessed tail included and not executed

FIFO domain owner

Hold if not verified

DLQ cohort rehearsal

Domain outcomes match approved manifest

Incident approver

Go, hold, or reconcile

The hard holds are deliberate. Stop if a message cannot be tied to a domain operation key, if an external effect is unknown and no reconciliation path exists, if a payload conflicts with a previously claimed key, or if FIFO order behavior has not been demonstrated for the workload’s actual grouping rules. Those conditions can turn a mechanically valid retry into a duplicate or out-of-order business action.

“Proceed” also has a bounded meaning. It means the tested class of work can run under the recorded configuration and evidence model. It does not certify exactly-once execution, prove all dependency failures are covered, or make future payload versions safe by inheritance. Re-run the relevant test when the business key, destination contract, batch settings, runtime, or mapping behavior changes materially.

Roll out the response contract as a reversible change

The dangerous rollout is to deploy a handler that catches record errors and returns batchItemFailures before verifying that the event-source mapping honors partial responses. Under the default batch contract, swallowing an exception can convert a record failure into an apparently successful invocation and allow deletion of work you intended to retry. The code and mapping therefore need a compatibility sequence.

Use a dual-mode application flag in the proposal. In legacy mode, collect per-record failures for telemetry but throw at the end if any record failed, preserving whole-batch failure semantics. Enable ReportBatchItemFailures on the mapping, read the mapping back, and only then switch the handler into partial mode. On rollback, switch the handler back to whole-batch failure first; a function-level exception is documented to fail the whole batch even while the mapping still supports partial reporting.

Change step

Gate before next step

Reversal

Inventory deployed state

UUID, settings, code revision captured

None; observation only

Deploy dual-mode code with partial mode off

Legacy failure test still returns whole-batch failure

Restore prior code

Enable ReportBatchItemFailures

Mapping read-back proves setting

Remove setting if code still legacy-safe

Enable partial mode for controlled exposure

Record/effect metrics explain test cohort

Turn partial mode off first

Expand exposure

No unknown-effect growth or duplicate business effects

Reduce exposure / legacy mode

Rehearse recovery

DLQ and rollback evidence complete

Hold rollout

A proposed 30-day rollout can keep the work paced without pretending AWS requires this schedule. Days 1–5: inventory configuration, domain keys and effect boundaries. Days 6–10: run the fake-destination and response-shape tests. Days 11–15: validate the disposable AWS mapping, including malformed response and invocation-wide failures. Days 16–20: expose a small, low-risk production cohort under change control. Days 21–25: rehearse reconciliation and bounded DLQ recovery. Days 26–30: review evidence and exercise rollback.

During exposure, retain the effect ledger even if partial reporting appears healthy. Rollback changes retry granularity: returning to whole-batch failure can make already successful records reappear, so removing the ledger at the same time would discard precisely the duplicate-protection evidence rollback needs. Preserve the affected record manifest across code and configuration changes.

Treat configuration drift as a rollback trigger, too. If the mapping read-back no longer matches the reviewed batch size, batching window, response type, source ARN, or enabled state, stop interpreting new observations through the old test evidence. Likewise, a runtime or dependency-client change can alter timeout and error behavior even when the business code is unchanged. Re-establish the minimum fixture before expanding exposure. Recoverability depends on knowing which contract was active for each cohort, not merely knowing that a deployment pipeline reported success.

This is an application of broader cloud development practices, but with a narrower reliability rule: configuration and code must move in an order that never assumes an acknowledgement contract that has not been verified. The change ticket should include stop criteria, rollback owner, and the read-back evidence, not just a deployment success message.

Build the cloud-development foundations behind the runbook

This playbook assumes skills that sit below the Lambda–SQS specialization: cloud architecture, deployment, infrastructure configuration, security, containers, monitoring, and operational diagnosis. The Refonte Learning Cloud Development Program page lists a three-month program at 12–15 hours per week and names cloud architecture, Docker, Kubernetes, infrastructure as code, cloud security, monitoring and optimization, plus building and deploying cloud applications. Those are useful foundations for understanding why this recovery design spans code, platform configuration and evidence.

Use a two-item readiness check before applying the runbook:

Readiness question

Minimum answer

Can you trace one record across queue delivery, handler execution and the external effect?

You can produce the identities and ledger transitions without relying on memory.

Can you explain why a retry is safe?

You can show confirmed, absent or reconciled effect evidence rather than pointing only to a Lambda status.

Dedicated Lambda–SQS partial-failure labs are not claimed here as program curriculum. The CTA is for the verified general cloud-development foundations, not a promise of this exact exercise or toolchain.

Keep the replay decision with the evidence

The durable result of a partial-batch design is not a promise that duplicates disappear. It is a record-level explanation that survives retry, interruption and dead-letter recovery.

Question

Answer

Does partial reporting remove duplicates?

No. It can reduce unnecessary retries of records reported successful, while Lambda–SQS at-least-once delivery still permits duplicate processing.

Does a successful invocation prove every effect?

No. The batch response and an external commit are not one atomic transaction.

What blocks replay?

Lost business identity, unknown side effects without reconciliation, unmitigated failure causes, or unverified FIFO ordering behavior.

The decision is therefore documented as go, hold, or reconcile. Replay only the work whose evidence supports it; preserve uncertainty until it can be resolved.