See it as a diagram
Everything below, as a diagram you can edit. Describe yours and see it in seconds.
No account needed · Editable canvas, not a picture
Drop the network chrome you do not have
API Gateway, Lambda, EventBridge, SQS, SNS, DynamoDB, and S3 are regional and fully managed. None of them sits in your subnets unless you put it there. Drawing an empty VPC container around a serverless workload adds a boundary that does not constrain anything and suggests network controls that are not in play.
The exception is worth drawing precisely because it is the exception. A Lambda attached to a VPC to reach an RDS instance, an ElastiCache cluster, or an interface endpoint really does have elastic network interfaces in your subnets, and that changes cold start behaviour, egress path, and security group rules. Draw the VPC only around the functions that are actually in it.
Label the invocation type on every arrow
There are three ways a Lambda gets called, and each carries a different retry and failure story. Synchronous invocation from API Gateway, an ALB, or a direct call means the caller waits and errors surface to the caller. Asynchronous invocation from S3 events, EventBridge rules, or SNS means AWS holds the event on an internal queue and retries it, and when the attempts run out the invocation record goes to the on-failure destination or the dead letter queue carrying condition RetriesExhausted and approximateInvokeCount, which is how you tell a poison event from a timeout. The retry count is per function, set with aws lambda update-function-event-invoke-config. Event source mappings for SQS, Kinesis, DynamoDB Streams, and MSK mean Lambda polls, batches, and blocks the shard or the batch when a record keeps failing.
Put that word on the arrow: sync, async, or poll, with the batch size where it matters. An on-call engineer reading the diagram at three in the morning needs to know whether a failed message is lost, retried, parked in a dead letter queue, or blocking a shard, and the arrow label is where that question gets answered.
Resources:
CheckoutApi:
Type: AWS::Serverless::Function
Properties:
Events:
Http: # synchronous, caller waits
Type: Api
Properties: { Path: /checkout, Method: post }
OrderWorker:
Type: AWS::Serverless::Function
Properties:
Events:
Queue: # event source mapping, poll based
Type: SQS
Properties: { Queue: !GetAtt OrderQueue.Arn, BatchSize: 10 }
EventInvokeConfig: # async failures land here
DestinationConfig:
OnFailure: { Type: SQS, Destination: !GetAtt OrderDlq.Arn }Draw the parts on-call needs
Dead letter queues and on-failure destinations belong on the canvas, not in an appendix. They are where messages go when the system is unhappy, and they are the first place anyone looks during an incident. The same goes for the EventBridge bus itself: draw it as a lane with the rules attached, because the routing rules are the coupling, and a diagram that shows producers and consumers without the bus hides where the matching happens.
Step Functions state machines read best as a single node with the workflow described in the accompanying document. Exploding fifteen states onto an architecture canvas buries the surrounding services, and the state machine definition is already a better diagram of itself.
One more thing serverless diagrams routinely omit: the datastore each function actually owns. Ownership is the architectural claim. Two functions writing the same DynamoDB table is a design decision worth seeing.
Two behaviours deserve an annotation rather than a node. Setting reserved concurrency to zero on an asynchronously invoked function stops retries entirely and sends new events straight to the dead letter queue, which is both the standard way to stop a runaway consumer and a reason a queue fills during an incident. And an SNS destination has a 256 KB message limit, so a function whose payload approaches 1 MB can fail to deliver its own failure record and report DestinationDeliveryFailures instead. Both are documented on the Lambda developer guide page on capturing records of asynchronous invocations, which also recommends SQS or S3 destinations for large payloads.
Grouping once there are forty functions
Per-function nodes stop working somewhere around fifteen. Past that, group by bounded context, one zone per domain, and draw the events that cross domain boundaries as the only inter-zone arrows. That produces a diagram that answers the question people usually have, which is which domain owns this behaviour, rather than a wall of identical Lambda icons. The edge discipline matters as much as the grouping: no more edges than nodes, and a fan of eight identical arrows from one bus to eight consumers replaced by one labelled flow into the zone.
Datadef draws exactly that shape from a description: zones per domain, real AWS icons, labelled edges. If the functions are defined in Terraform, the parse gives every aws_lambda_function its own node carrying runtime, memory_size, and timeout, because those are the three attributes the catalog picks for that type. Queues and topics are weighted lower, so four aws_sqs_queue resources in one module arrive as one node reading "SQS queues ×4: orders, orders dlq, exports, exports dlq" rather than four boxes competing with the functions. Export to PNG or JPEG for the deck, or embed the live image in the service README so the diagram follows the code.
FAQ
Do serverless diagrams need a VPC boundary?
How do I show the difference between sync and async invocation?
Should dead letter queues be in the diagram?
How should Step Functions appear?
What is the right grouping for a large serverless system?