One booking, end to end
After this you can answer: What happens, box by box, from the click on Book to the confirmation email?
Picture it
Section titled “Picture it”flowchart TB B[Browser] -->|"POST /trpc/bookings.create"| ALB[ALB] ALB -->|"port 4000"| API[API task] API -->|"session, limits"| V[(Valkey)] API -->|"booking + outbox row"| PG[(Postgres)] API -.->|"after the answer"| SNS[SNS topic] SNS --> Q[Jobs queue] Q --> L["Worker Lambda<br/>sends the email"]The solid arrows happen during the request. The dashed arrow happens after the user already has the answer.
How it works
Section titled “How it works”- Find the address. The browser looks up
minitickets.theversehub.in. Cloudflare DNS answers with the load balancer’s name. - Enter through the ALB. The browser sends
POST /trpc/bookings.createover HTTPS. A listener rule sends every/trpc/*path to the API’s target group, and the ALB picks a healthy API task. - Who is calling? The API reads the
sidcookie and loads the session from Valkey. No session means 401. - Is it too much? A rate limit allows a burst of 10 bookings, then one per second. A seat counter in Valkey rejects a sold-out event early with 409.
- Book it. One Postgres transaction locks the event row, takes the seats, inserts the booking and an outbox row (the “send the email” message), then commits. The browser gets its answer.
- After the answer. A relay loop in the API publishes the outbox row to an SNS topic. SNS copies it into the jobs queue, and a Lambda function sends the email with SES.
In MiniTickets
Section titled “In MiniTickets”The browser side is one hook, apps/web/src/features/bookings/hooks/use-book-ticket.ts → useBookTicket.
The server side is apps/api/src/services/bookings.ts → bookTicket, which
calls the locked transaction in apps/api/src/queries/bookings.ts → createBookingTx.
SNS also copies the event into a second queue, where another Lambda makes the QR ticket.
The map replays this exact path, one connection at a time, with the rule that allows each step.
Play “One booking, end to end” on the map →See it in AWS
Section titled “See it in AWS”CloudWatch → Log groups → /minitickets/dev/api: one booking writes a request line with its
correlationId. The worker’s log lines carry the same id.
aws logs tail /minitickets/dev/api --since 10m shows the latest API log lines.
The route of the request: apps/api/src/procedures/bookings.ts → bookingsRouter.
Break it
Section titled “Break it”| Symptom | Cause | Where to look |
|---|---|---|
| 401 on Book | No session: logged out, or Valkey lost its data | The sid cookie; ElastiCache events |
| 409 “Not enough seats left” | Someone else took the last seats first | Working as designed |
| Booking works, no email | The outbox row cannot be published, or SES refuses | API log, then the jobs DLQ |
Say it in an interview
Section titled “Say it in an interview”Follow-ups
Section titled “Follow-ups”Why not send the email inside the request?
SES could be slow or down. Then every booking would be slow or fail, although the seats are already safe. The outbox row commits with the booking, so the email is never lost and never blocks the answer.
What if the API crashes right after the commit?
The booking and its outbox row are both saved. When an API task runs again, its relay finds the unsent row and publishes it. The user may get the email a little later, never not at all.
What if the user double-clicks Book?
Both requests carry the same idempotency key. The second one finds the first booking and returns it; no seats are taken twice. Part 3 explains how.
What is the trade-off of doing the email later?
The user sees “Booked!” before the email exists. If sending keeps failing, the message ends in a dead-letter queue and an alarm fires; a person must act. That is the price of a fast, safe booking.
Advanced: Every step and its rule
The same flow, generated from the book’s data, with the exact connection each step uses:
A logged-in user clicks Book. We follow the request from the browser to the database, and the confirmation email after the response.
- Browser → Public DNS. The browser looks up minitickets.theversehub.in. Cloudflare answers with a CNAME to the ALB's DNS name.If it breaks: The CNAME still points at an old ALB: the browser gets a DNS error. Look: dig +short CNAME minitickets.theversehub.in versus pulumi stack output albDnsName.
- Browser → Application Load Balancer. The browser sends POST /trpc/bookings.create over HTTPS. The ALB security group allows inbound TCP 443.If it breaks: No inbound 443 rule: the connection times out. Look: EC2 → Security Groups → minitickets-dev-alb-sg → Inbound rules (443 from allowedCidrs).
- Application Load Balancer → HTTPS listener. The :443 listener ends TLS with the ACM certificate.
- HTTPS listener → API target group. Listener rule 100 matches /trpc/* and forwards to the api target group.If it breaks: Without the rule, the default action sends the call to web, which answers 404. Look: Listeners and rules → HTTPS:443 → Rules.
- API target group → API ECS service. The ALB picks a healthy api task and connects on port 4000, allowed by api-sg's inbound rule from alb-sg.If it breaks: No healthy target: the ALB answers 503. Look: EC2 → Target groups → the api group → Targets.
- API ECS service → API app. Fastify gives the request a correlation id, and tRPC runs bookings.create (a protectedProcedure).
- API app → Sessions. createContext loads the session from the sid cookie. No session means 401.If it breaks: Valkey lost its data: every user is logged out. Look: ElastiCache → minitickets-dev-valkey → Events; the api log.
- API app → Rate limiter. The booking bucket gives a token (burst 10, then 1 per second).If it breaks: The bucket is empty: 429 Too Many Requests. Look: The api log: 429s per client IP; TRUST_PROXY_HOPS.
- API app → Inventory gate. The inventory gate reserves the seats in Valkey. Sold out would be a 409 right here.
- API app → RDS Postgres. One transaction: lock the event row (SELECT … FOR UPDATE), insert the booking and its outbox row, commit. The api answers the browser.If it breaks: The db security group drops the packets: connection timeout, the booking fails. Look: EC2 → Security Groups → minitickets-dev-db-sg; ECS → the task → Network.
- Transactional outbox → Booking-events SNS topic. After the response, the outbox relay publishes the booking-confirmed event to SNS with the task role's sns:Publish.If it breaks: The task role may not publish: the row stays pending and is retried. Look: The api log group, and IAM → minitickets-dev-api-task-role → send-jobs.
- Booking-events SNS topic → Jobs SQS queue. SNS copies the event into the jobs queue (and the tickets queue), allowed by each queue's resource policy.If it breaks: The queue policy does not allow this topic: nothing arrives. Look: SQS → the queue → Access policy; SNS → the subscription's delivery status.
- Jobs SQS queue → Worker Lambda. The event source mapping polls the queue and calls the worker Lambda with a batch.If it breaks: The account's Lambda concurrency is used up: the worker is throttled. Look: Lambda → Monitor → Throttles; aws lambda get-account-settings.
- Worker Lambda → Processed-jobs table. GetItem on jobKey: this email was not sent yet.
- Worker Lambda → SES email identities. SES sends the confirmation from no-reply@minitickets.theversehub.in, then the worker records the job as done.If it breaks: In the sandbox SES refuses an unverified recipient. Look: SES → Account dashboard (sandbox), SES → Identities.
Checkpoint
Section titled “Checkpoint”Answer these without scrolling up. If one feels hard, reread that section.
- Which listener rule sends the booking to the API?
- What two things does the API check in Valkey before it touches Postgres?
- What does the one Postgres transaction write?
- Why is the email sent after the answer, and how is it never lost?
- What does the user see if Valkey loses all its data?
Redraw it from memory: the booking path: browser, ALB, API task, Valkey, Postgres, then SNS, the jobs queue and the worker Lambda
Try this: on the map, play the booking flow and open 'What if it breaks?' on the Valkey step
Read this book online: https://book.theversehub.in/