AWS Builder Center
Event-driven architecture on AWS: a beginner's guide

Event-driven architecture on AWS: a beginner's guide

Learn how event-driven architecture works on AWS through a food delivery order, from EventBridge, SNS, and SQS to retries and the $86 burger problem. Then build your first event-driven app with your coding agent and Agent Toolkit for AWS.

Developer Relations Engineer
You tap Place Order in a food delivery app. A restaurant starts cooking, the payment service charges your card, a confirmation lands in your inbox, and the rewards system adds your points. Four systems did four jobs off one tap, and your burger now has a distributed systems problem.
Event-driven architecture is how a lot of AWS applications handle that kind of work. This guide follows the food order from the companion video and goes further: what travels across those arrows, how queues deal with failure, why duplicates happen, and how to pick the smallest design that works. At the end you'll build Upload Lab, a private file-intake app that turns an S3 upload into a DynamoDB record, with your coding agent and Agent Toolkit for AWS doing most of the typing.
You need a basic understanding of applications and API requests. You don't need any queue experience, though standing in line for coffee counts as research.

What is event-driven architecture?

Event-driven architecture is a way to build applications where components publish records of things that happened, and other components react to those records. The component that publishes an event doesn't need to know who's listening.
In the food-order example, the Order Service doesn't call every downstream system. It publishes one OrderPlaced event, and the restaurant, notifications, and analytics each react on their own schedule.
Three terms cover the basics:
TermMeaningFood-order example
ProducerCreates and publishes an eventOrder Service
EventA record that something happenedOrderPlaced
ConsumerReceives an event and does something useful with itConfirmation sender

What's the difference between an event, a command, and a message?

An event says "this happened." A command says "do this." A message is a unit of data sent between components, and it can carry either one. A job is a unit of work someone has to finish.
OrderPlaced is an event. SendConfirmationEmail is a command. Either can travel inside a message, and sending the email is the job.
Event names tell consumers what's true. OrderPlaced doesn't mean PaymentCompleted, and neither one means OrderDelivered. Have each consumer react to the specific fact it needs instead of assuming the whole business process finished because the first event showed up.
An event describes a fact. Each consumer decides what that fact means for its own work.
In this example payload, eventId identifies one occurrence and orderId identifies the order. One order produces several events over its lifetime. schemaVersion names the agreed data format, or schema, so producers and consumers can change over time without guessing what a field means. These fields come from our example contract. There's no universal event format, and each AWS service defines its own.

What is loose coupling, and why does it help an application grow?

Loose coupling means components depend less on each other's implementation, availability, and timing. Each one can change, fail, or scale without dragging the others along.
Say you want to add rewards tracking. In a direct-call design, someone has to add another integration to the Order Service. If OrderPlaced already exists, you write a rewards consumer that subscribes to it and leave the order flow alone.
The producer still has work to do. It has to publish events reliably, define what they mean, and keep the data format compatible. Consumers depend on that contract. Loose coupling doesn't mean everyone improvises JSON and hopes for the best.
Publishing needs its own failure plan too. Saving an order and publishing its event are two separate operations unless you design them otherwise, so the database write can succeed while the publish fails. The transactional outbox pattern  handles this: you record the order and the intent to publish together, and a separate process delivers the event. Upload Lab sidesteps the problem by letting S3 generate the events, which keeps it out of your first build.

Synchronous vs. asynchronous processing: what's the difference?

Synchronous processing makes the caller wait for a result. Asynchronous processing lets the caller move on while the remaining work happens later.
The restaurant analogy from the video fits. A server takes your order, hands it to the kitchen, and goes back to other tables. They don't stand next to you for twenty minutes making intense eye contact with a bread roll.
Use synchronous calls when the answer decides what happens next. Is the restaurant open? Is the delivery address valid? If either answer is no, the app shouldn't accept the order.
Analytics and rewards can catch up later. Your customer shouldn't stare at a spinner because a dashboard is having a rough evening.
Asynchronous doesn't mean instant, successful, or faster. It changes who waits and for how long. Your app should track separate states for accepted, processing, completed, and failed, and it shouldn't declare victory the moment work lands in a queue.

EventBridge vs. SNS vs. SQS: when should you use each?

EventBridge routes events based on their content, SNS copies one message to many subscribers, and SQS holds work until a consumer is ready for it. Lambda runs the code that does the work. The services overlap in places, so pick based on the behavior you need.
ServiceWhat it doesReach for it when
Amazon EventBridgeRoutes events to targets using rules that match event contentDifferent events need to reach different places
Amazon SNSPublishes one message to a topic and delivers a copy to each subscriberSeveral subscribers need the same message
Amazon SQSHolds messages in a queue until a consumer processes themWork arrives faster than you can process it, or you need retries
AWS LambdaRuns your function code in response to events or queue messagesYou need code that processes the event
Route, fan out, and buffer: choose the behavior your application needs.

Amazon EventBridge: route events to matching targets

An event bus receives events and routes them according to rules. Upload Lab uses the default bus with one rule, where an event pattern selects matching events and sends them to a target.
OrderPlaced might go to restaurant operations and analytics, while OrderCancelled goes to refund processing. Several rules can match the same event, so routing can also give you fan-out. The EventBridge event bus documentation  covers the options.

Amazon SNS: publish once, deliver to every subscriber

Publish/subscribe, or pub/sub, lets a producer publish to a topic, and each subscriber gets its own copy. One-to-many delivery like this is called fan-out.
A notifications component could publish one message to an SNS topic that has email and app subscribers. SNS also has subscription filter policies, so a subscriber can receive only the messages that match. "SNS broadcasts everything to everyone" works as a first sketch, but it leaves out SNS message filtering .

Amazon SQS: give work somewhere to wait

A queue holds messages until consumers process them. Picture kitchen tickets on a rail: orders can come in faster than the cooks can finish them, and the rail keeps them waiting in line.
SQS standard queues  use at-least-once delivery. You can get duplicates, and messages can arrive out of order, so design your consumer for both. Queues also have a finite retention period, which means a growing backlog still needs monitoring.

AWS Lambda: process messages from the queue

When Lambda reads from SQS, an event source mapping polls the queue, receives messages, and invokes your function. The overall workflow is asynchronous, even though the mapping invokes your function synchronously behind the scenes. AWS documents the details in using Lambda with Amazon SQS .
More function instances can clear a backlog faster, within your configured limits and quotas. But if every instance calls the same kitchen display that accepts three orders per second, adding instances makes the bottleneck worse. Cap concurrency and respect what downstream systems can handle. You can't autoscale a single deep fryer with enthusiasm.

Do multiple consumers need separate SQS queues?

Yes, if each consumer needs its own copy of every message. Three workers doing the same job can share one queue and split the work. Restaurant processing and analytics need separate queues if both care about every order.
Use EventBridge targets or SNS subscriptions to deliver a copy to each queue. Each consumer then gets its own backlog, processing rate, and failure recovery. If they share a queue, the analytics worker can grab the ticket the kitchen needed. Your dashboard gets dinner and you don't.

How do SQS retries, visibility timeouts, and dead-letter queues work?

A message stays in the queue after a consumer receives it. The visibility timeout hides it from other consumers while processing runs.
For a Lambda consumer, the cycle goes like this:
  1. Lambda receives the message and invokes your function.
  2. If processing succeeds, the integration deletes the message from the queue.
  3. If processing fails and nobody deletes the message, it becomes visible again after the visibility timeout and gets retried.
  4. After enough failed receives, the queue's redrive policy moves the message to a dead-letter queue (DLQ).
The visibility timeout doesn't guarantee exactly-once processing. It coordinates who's working on what, and your consumers still need to handle duplicates. The SQS visibility timeout guide  has the specifics.
A failed message can be retried. A DLQ isolates persistent failures so an operator can investigate and recover them.

Does a dead-letter queue fix failed messages?

No. A DLQ is a holding area, not a tiny mechanic.
You investigate the error, fix the cause, and redrive the messages back to the source queue. Set the DLQ's retention period on purpose and monitor it, because messages expire there too. One detail trips people up: maxReceiveCount counts message receives, which isn't the same as counting business-operation attempts. The SQS dead-letter queue docs  explain the redrive options.
Two different failures need attention. EventBridge can fail to deliver an event to SQS, or Lambda can fail after SQS already has the message. An EventBridge target DLQ covers the first case, and the SQS processing DLQ covers the second. Upload Lab includes the processing DLQ and leaves EventBridge target DLQs  as a hardening step for later.

What is idempotency, and how does it prevent duplicate charges?

Idempotency means repeating an operation has the same intended effect as running it once. In an event-driven system, it's how a consumer survives duplicate delivery.
The video imagines a $42.99 payment that succeeds, followed by a crash before the consumer acknowledges the message. SQS delivers the message again, and the retry charges another $42.99. That's one order for $85.98, and the $86 burger has entered the chat.
Duplicate delivery can happen. Your consumer has to stop it from turning into a duplicate business effect.
A production payment flow needs more than "check a table, charge, then mark it processed." Two workers can run the check at the same moment. A crash can also land after the charge but before you record success.
Use a stable business-operation key with your payment provider's idempotency support. Create the key once for the intended charge and reuse it on every retry. Add durable state and reconciliation for outcomes you can't confirm. A transport message ID on its own may miss duplicates of the same business operation. The Amazon Builders' Library article on making retries safe with idempotent APIs  explains how caller-supplied request IDs preserve that intent.
Upload Lab has a gentler side effect: it writes one metadata item. A deterministic object identity plus an atomic conditional write prevents duplicate inserts. DynamoDB can reject a write when the item already exists, which closes the gap between a separate check and insert. The DynamoDB condition expressions guide  shows the syntax.
That's why your first project processes harmless text files instead of credit cards. Your learning curve doesn't need a chargeback department.

What is eventual consistency in an event-driven application?

Eventual consistency means different components can show different states for a while because they process updates at different times. Your order can exist while the rewards page still shows yesterday's points.
Once delivery and processing succeed, those views catch up. "Eventually" won't repair a consumer that keeps failing, though. Show pending states to users, track processing delays, and investigate work that gets stuck.
In Upload Lab, S3 can confirm your upload before DynamoDB has the metadata. Asynchronous processing creates that gap, even if each individual storage read is strongly consistent. The question to ask is "has the worker finished?"

When should you use event-driven architecture on AWS?

Event-driven architecture fits when you see one or more of the seven signals from the video:
  • Several systems need to react to the same thing.
  • You expect to add new consumers over time.
  • Traffic arrives in bursts.
  • Parts of the system need to scale independently.
  • Some work can happen later.
  • You want one failure to stay contained.
  • Your process produces a series of events on its own, like placed, paid, cooked, and delivered.
Add a service only when it solves a requirement you have.
For one simple upload reaction, S3 → Lambda already counts as event-driven. You don't need a bus to qualify. Add routing or queues when you need what they do. S3 can send event notifications  straight to Lambda or deliver them through EventBridge.
Keep simple request/response interactions simple. If you have a tightly controlled sequence where step B has to wait for step A, look at workflow orchestration with a service like AWS Step Functions. Events alone won't enforce that order.
File safety is a good example. A malware scanner and a public-sharing consumer shouldn't race each other on the same ObjectCreated event. Keep the object private or quarantined, and make release or downstream processing depend on a clean scan result for that exact object version. A file showing up in storage doesn't prove it's safe.

Tutorial: build an event-driven app on AWS with S3, EventBridge, SQS, and Lambda

In this tutorial you'll build Upload Lab with your coding agent. You'll upload a small text file, watch its event move through a queue, and see a Lambda worker save the file's metadata to DynamoDB. Start with the new AWS signup experience .

Step 1: set up your AWS account and Agent Toolkit for AWS

The new signup experience supports Google, GitHub, Apple, and Amazon sign-in and creates an initial project for you. AWS advertises credits for eligible new customers and no credit card requirement for most new customers, so check the terms shown for your account. The rollout is still limited, and your signup flow may look different. The AWS launch announcement  and signup documentation  have the details.
Agent Toolkit for AWS connects AI coding agents to AWS so they can build and manage applications with you. To set it up, open AWS Settings  and go to your project → Create and manage cloud infrastructure → Use your AWS credentials with AI coding tools → Copy agent prompt. Give that setup prompt to your agent. It walks through installing the AWS CLI, the toolkit, and your project-specific rules. Handle the authentication steps yourself. The Agent Toolkit overview  and the guide to connecting your AI coding tool  cover the rest.
Before you deploy, find your project's Region under AWS Settings → your project → Overview → Additional Info, and use it for the whole lab. The toolkit's own service endpoint Region is a separate setting. See project Regions  for how they differ.

Step 2: review the Upload Lab architecture

Solid arrows show the starter project. The SQS processing DLQ catches messages that keep failing after they reach the work queue.
The flow has six steps:
  1. You upload a small, uniquely named .txt file under uploads/ in a private S3 bucket.
  2. S3 sends an Object Created event to the default EventBridge bus.
  3. An EventBridge rule matches the source, event type, bucket, and uploads/ key prefix.
  4. An SQS standard queue holds the message until the worker is ready.
  5. A Lambda function reads the object's metadata and conditionally writes one DynamoDB item.
  6. A processing DLQ catches persistent failures so you can investigate and redrive them.
Once you turn on EventBridge integration for the bucket, S3 sends all its supported events to the bus, and your rule picks the ones you care about. The S3 and EventBridge guide  lists them.
Upload Lab is a learning app for your own harmless files. The user journey is upload → processing → processed result. Start with a CLI for uploading files and looking up results. Give lookups a bounded wait that can report "result not ready," so you don't mistake a missing row for healthy processing. A public website, authentication, malware scanning, and AI processing would each be a separate lesson.

What does an S3 event look like in EventBridge?

The file stays in S3. The event carries a pointer and metadata, never the file contents. This shortened example shows the fields that matter for the lab. Real events include more envelope fields, documented in the S3 EventBridge event structure .
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"id": "example-event-id",
"source": "aws.s3",
"detail-type": "Object Created",
"detail": {
"bucket": { "name": "your-upload-lab-bucket" },
"object": {
"key": "uploads/unique-id-notes.txt",
"size": 128,
"version-id": "example-object-version"
}
}
}
When EventBridge sends the full event to SQS unchanged, Lambda receives an outer Records array. Each record's body is a string that contains the EventBridge JSON. Parse that body before you read detail.bucket.name. The layers are packaging, and there's still only one file.

Step 3: build Upload Lab with five coding agent prompts

Run these prompts in order. They ask your agent to use infrastructure as code (IaC), so your AWS resources live in project files you can review. They also ask the agent to explain its choices, which is where most of the learning happens.

Prompt 1: plan the architecture and set up the project

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Help me build Upload Lab while teaching me event-driven architecture.
Use Agent Toolkit for AWS and current official AWS documentation. Inspect
the available tools and authenticated project context; don't invent tools.

Plan this path: private S3 bucket -> default EventBridge bus rule -> SQS
standard queue -> Lambda metadata worker -> DynamoDB, plus an SQS
processing DLQ. Upload and inspect results with a local CLI initially.
Accept only my small, harmless text-file test inputs with unique keys.

Explain producer, event, routing, queue, consumer, duplicate delivery,
and eventual consistency in this design. Choose one supported IaC
framework and language, explain prerequisites, and scaffold local files.
Prefer one CloudFormation or SAM stack for this small lab. Follow my
AWS project rules and verify profile, project, Region, and plan. Identify
cost drivers, IAM needs, and cleanup behavior. Show the plan and local
validation steps before creating billable resources.

Prompt 2: build the routing and a duplicate-safe worker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Implement the agreed IaC and worker. Enable S3 EventBridge delivery.
Match source aws.s3, detail-type Object Created, the exact bucket, and
object-key prefix uploads/. Send the full EventBridge event to SQS.
Restrict queue publishing to the intended rule. Keep the bucket private.

Enable object versioning. Identify work by bucket + key + version ID;
read that exact version's metadata. Validate the event shape and fail
clearly for unsupported inputs. Parse each SQS Records entry's body.
Write size, object identity, event ID, and completion information to
DynamoDB using an atomic conditional write. Treat an existing successful
result as duplicate success, not as a failure to retry. Do not store
file contents or add a separate check-then-write race.

Add scoped runtime permissions, structured logs without file contents,
a processing DLQ, bounded concurrency, and deliberate retention. Enable
ReportBatchItemFailures on the event source mapping and return the failed
SQS message IDs from the handler. Verify visibility timeout, function
timeout, batch window, and maxReceiveCount against AWS guidance.
Run local tests and synthesize or validate the infrastructure. Present
the resource changes and deployment commands for my review.
AWS recommends setting the queue's visibility timeout to at least six times the Lambda function timeout, plus the batch window if you use one, and setting maxReceiveCount to at least five. Those values give transient failures room to retry before SQS moves a message to the DLQ. The Lambda SQS configuration guide  explains the reasoning.

Prompt 3: deploy and test the happy path

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
After I approve the resource plan, deploy only this lab to my selected
AWS account/project and its assigned Region. Confirm identity and target
first; use the configured authentication without exposing secrets.

Upload a tiny uniquely named text file under uploads/. Show the event
rule, queue activity, worker logs, and resulting DynamoDB metadata.
Explain why upload completion may precede the metadata result. Poll
with a bounded timeout rather than assuming an instant result.

Test a key outside uploads/ and verify that it produces no worker
result. Replay a captured SQS-shaped payload to test the worker with the
same bucket, key, and version ID; verify that only one metadata item
exists. A new upload creates new work, not a duplicate of that version.
Use scoped test permissions and explain that this replay tests the worker,
not the S3-to-EventBridge transport. Don't forge aws.s3 events via PutEvents.
Report evidence and limitations, not just a deployment success message.

Prompt 4: trigger a controlled failure and recover

1
2
3
4
5
6
7
8
9
10
11
Design a lab-only test that makes one designated test object fail in
the worker. Show me the temporary change before enabling it. Demonstrate
receive counts, visibility timeout behavior, and eventual arrival in
the SQS processing DLQ. Keep successful messages processing normally.

Then remove the fault, redrive the affected lab messages at a modest
rate, and verify one successful metadata result. Also demonstrate a
small bounded backlog and how the concurrency limit affects draining.
Restore normal settings and prove recovery. Explain why this processing
DLQ does not capture EventBridge-to-SQS delivery failures. Document
that separate failure path and its monitoring as a hardening step.

Prompt 5: review what you learned and clean up

1
2
3
4
5
6
7
8
9
10
Create a short learning report linking each concept to evidence from
our tests. Explain how to add a second independent consumer with its
own queue, and why two different consumers sharing one queue is wrong
if both need every event. Keep this extension as a plan for now.

Prepare cleanup for this lab only. List the resources and stored data
to remove, including S3 versions/delete markers, queues, DynamoDB,
rules, mappings, IAM roles, and logs. Explain retained resources and
shared framework bootstrap resources. After I approve deletion, remove
the lab, verify the remaining inventory, and report anything left.

What should you build after Upload Lab?

Add a second queue and consumer for an independent reaction. You could add a summarizer that reads approved text and calls an AI model, with its own permissions, costs, and failure handling. If you plan to accept uploads from other people, build the scan gate before you open up access or add processing.
Your first milestone is smaller: upload one file, trace one event, survive a duplicate, recover from a failure, and clean up. If you can explain why each box in the diagram is there, you're ready to sketch your own.

Event-driven architecture on AWS: frequently asked questions

Is event-driven architecture the same as serverless?

No. Event-driven architecture describes how components communicate: they react to events instead of calling each other directly. Serverless describes how you run code without managing servers. The two pair well on AWS, since Lambda, EventBridge, SNS, and SQS are all serverless, but you can build an event-driven system on containers or EC2 instances too.

Does Amazon SQS deliver each message exactly once?

Standard queues use at-least-once delivery, so duplicates can happen. FIFO queues add ordering and exactly-once processing within a five-minute deduplication window, and they have their own throughput quotas. Write idempotent consumers either way, because a crash in your own code can still repeat a side effect.

Is S3 to Lambda event-driven?

Yes. An S3 event notification that invokes a Lambda function is a complete event-driven design. Add EventBridge when you need to route events to several targets based on their content, and add SQS when you need a buffer between bursts of uploads and your worker.

Should I use EventBridge or SNS for fan-out?

Both can deliver one event to several targets, and both can filter. I'd start with EventBridge when routing logic matters or when the events come from AWS services, like the S3 events in Upload Lab. SNS fits simple, high-volume fan-out to a known list of subscribers.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article