7 views
This guide covers Lambda destinations with production-focused examples and trade-offs.
A classic DLQ stores the original event — useful, but often not enough to replay safely. On-failure destinations add invoke context (request id, function arn, approximate timestamp, response payload) so remediator tools can route, dedupe, and replay without archaeology.
⚡ TL;DR: For async invokes, configure OnFailure to SQS or EventBridge with a versioned envelope schema; build a remediator that validates, dedupes by idempotency key, and re-invokes deliberately; keep DLQs as backup for stream sources. Pair with Lambda Recursive Loop Detection and Lambda Timeouts & DLQs.
Destinations vs DLQ — Lambda destinations
| Source | Failure sink | Notes |
|---|---|---|
Async InvocationType=Event |
On-failure destination | Rich context |
| S3/SNS/EventBridge async | On-failure destination | Prefer over only CloudWatch Logs |
| SQS/Kinesis/DDB streams | ESM partial failure + DLQ | Destinations do not replace stream DLQs |
typescript
// cdk/destinations.ts
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as destinations from "aws-cdk-lib/aws-lambda-destinations";
import * as sqs from "aws-cdk-lib/aws-sqs";
import * as events from "aws-cdk-lib/aws-events";
const failQ = new sqs.Queue(this, "FnOnFailure", {
retentionPeriod: Duration.days(14),
});
const bus = events.EventBus.fromEventBusName(this, "Bus", "platform");
const fn = new lambda.Function(this, "AsyncWorker", {
runtime: lambda.Runtime.NODEJS_20_X,
handler: "index.handler",
code: lambda.Code.fromAsset("dist"),
retryAttempts: 2,
onFailure: new destinations.SqsDestination(failQ),
// alternatively: new destinations.EventBridgeDestination(bus)
});
Envelope schema for replay
Normalize destination payloads into your own schema the moment they land (EventBridge rule → remediator, or SQS consumer).typescript
// types/failureEnvelope.ts
export type FailureEnvelopeV1 = {
schema: "lambda.failure/v1";
functionArn: string;
requestId: string;
timestamp: string;
idempotencyKey: string; // from original event or hash
payload: unknown; // original async event
error?: { type: string; message: string };
};
export function fromDestinationRecord(rec: any): FailureEnvelopeV1 {
const req = rec.requestContext ?? rec.requestPayload ?? {};
const payload = rec.requestPayload ?? rec;
const key =
payload?.idempotencyKey ??
hashStable(payload);
return {
schema: "lambda.failure/v1",
functionArn: rec.functionArn ?? req.functionArn,
requestId: rec.requestContext?.requestId ?? "unknown",
timestamp: new Date().toISOString(),
idempotencyKey: key,
payload,
error: rec.responsePayload?.errorMessage
? { type: rec.responsePayload.errorType, message: rec.responsePayload.errorMessage }
: undefined,
};
}
Remediator pattern
typescript
❌ Auto-replaying every on-failure message into the same function version that just failed — without a circuit breaker.
// remediator.ts
export async function replay(env: FailureEnvelopeV1, mode: "dry-run" | "live") {
if (await alreadySucceeded(env.idempotencyKey)) return { status: "skip" };
if (mode === "dry-run") return { status: "would_replay", env };
// ✅ Invoke updated alias deliberately — not the broken $LATEST by accident
await lambda.send(new InvokeCommand({
FunctionName: `${env.functionArn}:live`,
InvocationType: "RequestResponse",
Payload: Buffer.from(JSON.stringify(env.payload)),
}));
await markSucceeded(env.idempotencyKey);
return { status: "replayed" };
}
Closing checklist
✅ Dos– ✅ Use on-failure destinations for async invokes
– ✅ Version an envelope schema; index by idempotency key
– ✅ Separate remediator function + alias targeting
– ✅ Retain failure queues ≥ 14 days for incident windows
– ✅ Alarm on destination queue depth ❌ Don’ts
– ❌ Don’t confuse stream source DLQs with async destinations
– ❌ Don’t unbounded auto-replay
– ❌ Don’t drop requestId — you need it for X-Ray/logs
– ❌ Don’t publish failures back onto the originating bus without hop limits
Related reading
- Lambda Timeouts, Retries, and DLQs
- Lambda Recursive Loop Detection
- Lambda Alias Traffic Shifting
- AWS Lambda Best Practices
Related: Lambda SnapStart for Python: Pitfalls Beyond the Java Marketing · SQS FIFO + Lambda: Ordered Agent Job Queues Without Double-Applies · Lambda SQS Partial Batch Failures: Poison Messages Without Infinite Redrive
Watch: 100 Days of AI on YouTube
Short videos from CheatCoders, one AI engineering topic per day.
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Claude Code vs Cursor vs GitHub Copilot: Choose by Workflow, and Set Up One Repo That Works With All Three
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
- Python String Methods: Every str Method With Real Production Examples
- Css Animation Performance: Performance-Optimized Motion for Modern UIs
Newly added
- AWS Systems Manager Parameter Store: Hierarchical Runtime Config for Coding Agents
- Amazon Cognito Identity Pools: Federate Coding-Agent Tool Callers Without Long-Lived Keys
- Amazon Athena for Coding Agents: Query Tool Audit Logs and Eval Traces on S3 Without a Warehouse
- AWS Batch for Coding Agents: Parallel Overnight PR Reviews and Long-Running Jobs Without Lambda Timeouts
- Amazon Bedrock Prompt Management: Version and A/B Test Coding-Agent System Prompts
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
