Case studies Technical

AWS serverless architecture, with the receipts

Most explanations of serverless stop at the diagram. This one runs all the way to a system you can poke at: the website you are on. Every number, service and decision below is the one actually in production, including the parts we got wrong first.

01

What serverless actually is

There are still servers. They are just not yours. What changes is the unit you rent and the unit you think about: instead of a machine that runs all month, you rent one execution of one function, and you are billed for the milliseconds it was awake.

That one shift pulls a lot behind it. There is no operating system to patch, because you do not have one. There is no capacity to plan, because there is no fixed capacity — a thousand simultaneous requests start a thousand copies. And there is no idle cost, because between requests nothing of yours exists.

What a rented server bills for, against what a function bills for One day on a single timeline. Four requests arrive at the same four moments in both rows. The rented server is billed across the whole day, shown as one unbroken band. The function is billed only at those four moments, shown as four narrow marks with nothing between them. One day. The same four requests. What each one bills you for. 00:00 06:00 12:00 18:00 24:00 Four requests arrive A rented server billed all day BILLED — 24 hours, whether or not anything happens A function billed per request four narrow marks — nothing billed in between Server: paid for 24 hours. Used a few seconds of it. Function: paid for a few seconds. The bill follows the work.
The whole idea in one picture: the paid time stops being a rectangle and starts matching the work.

What you give up

Worth saying before the good parts. You lose the machine, so anything that wants a long-lived process, a local disk or a fixed IP has to be rethought. The first request after a quiet spell is slower. Your architecture becomes harder to lift to another cloud. And debugging moves from “ssh in and look” to reading logs and traces, which is a different skill.

02

The services it takes

A serverless stack is assembled rather than installed. These are the pieces this site uses, what each one replaces, and what you pay for.

Amazon S3

Replaces the web server

Object storage that answers HTTP directly. The built site is copied into a bucket and served from it. Nothing runs; files are returned.

Billed per GB stored and per request

AWS Lambda

Replaces the application server

Runs a function on demand, in a container AWS starts and destroys. You supply code and a memory size; AWS supplies everything under it.

Billed per millisecond × memory

Lambda Function URL

Replaces the API gateway

One HTTPS endpoint attached straight to a function, with its own CORS settings. For a single endpoint it removes a whole service from the diagram.

No extra charge

Amazon DynamoDB

Replaces the database server

A managed key-value store with no instance to size. Set a time-to-live on a row and it deletes itself when it expires.

Billed per read, write and GB

Amazon SNS

Replaces the mail server

Publish a message to a topic and everyone subscribed to it is notified. For an enquiry alert that is the entire requirement.

Billed per message

IAM with OIDC

Replaces stored credentials

The deploy pipeline proves who it is with a short-lived token from GitHub and assumes a role. No AWS access key exists to leak.

No charge

Not used here, but part of the same family and worth knowing: API Gateway when you need many routes, authorisers or throttling; Step Functions when work spans several functions; EventBridge for scheduling and fan-out; CloudFront when you want a CDN and a custom certificate in front of S3.

03

One request, end to end

This is what happens when you fill in the contact form on this site and press Send.

The path a form submission takes through the architecture The browser loads pages from Amazon S3. On submit it posts to a Lambda Function URL, which invokes a Lambda function. The function writes the enquiry to DynamoDB with a time-to-live and publishes a notification to SNS, which emails the team. The function then returns a JSON reply to the browser, which shows the thank-you page. Browser the visitor fills the form Amazon S3 the pages themselves GET / Function URL one HTTPS door CORS lives here POST AWS Lambda starts, runs, stops checks the honeypot DynamoDB the enquiry, with a TTL Amazon SNS emails the team 200 { ok: true } → thank-you page
Nothing in the middle of this diagram exists until the POST arrives, and none of it exists a second after the reply.
  1. The pages arrive from storage

    The site is built to static files and synced to an S3 bucket. S3 answers the request itself. There is no process in front of it to run out of memory, no connection pool, and nothing that falls over when a newsletter goes out.

  2. You press Send

    The browser posts JSON to the Function URL. Until this moment there is no compute of ours running anywhere in the world — not idling, not scaled to zero, simply absent.

  3. AWS starts a container

    It pulls the function’s code, starts a runtime, and calls the handler. On a warm container it skips straight to the handler. Either way the function is one file of JavaScript with no framework under it.

  4. The function decides whether this is a person

    Three cheap checks, in order: a hidden field that only an automated form-filler would populate, a comparison between when the page rendered and when it was submitted, and a per-IP counter kept in the same table. A bot fails at least one.

  5. It writes, it tells, it replies

    The enquiry goes into DynamoDB with an expiry stamped on the row, a notification goes to SNS, and a small JSON reply goes back to the browser, which sends you to the thank-you page. The container is then frozen, and shortly after, discarded.

04

This website, in detail

The specifics, so you can judge the claims rather than take them.

Front end
Vite multi-page build, Three.js scenes, no framework runtime
Hosting
Amazon S3 static website hosting
Region
ap-south-1 (Mumbai) — the data stays in India
Compute
One AWS Lambda function, Node.js, a single file
Entry point
Lambda Function URL, CORS configured on the URL
Store
DynamoDB, one table, TTL attribute on every row
Notifications
Amazon SNS topic, email subscription
Bot defence
Honeypot field, render-time clock, per-IP counter
Deploys
GitHub Actions assuming an IAM role via OIDC
Languages
English, Hindi and Marathi, built as whole pages

Three things we got wrong first

CORS set in two places

We configured CORS on the Function URL and also returned the headers from the function. The response then carried the access-control-allow-origin header twice, browsers rejected it as malformed, and the form showed “Load failed” — while the enquiry was stored and the email sent perfectly well. The fix is to pick one place. We pick the URL.

A public endpoint that looked alarming

The Function URL is visible in the page source, which is the usual first worry. It is deliberately public: it accepts a form post and nothing else, holds no credentials of its own, and its IAM role can write to exactly one table and publish to exactly one topic. The secret is not the address; it is what the address is allowed to do.

A CAPTCHA we did not need

The first design used a third-party CAPTCHA. That meant another vendor in the request path, another privacy disclosure, and a puzzle for every genuine visitor. The honeypot-and-clock approach costs nothing, tells no one anything, and has held.

05

Cold starts, honestly

When no container is warm, AWS has to create one before your code runs. That is a cold start, and it is the most-cited objection to serverless. It is real, and it is also usually quoted by people describing a different runtime than the one you are using.

What actually governs it: the language, the size of the deployment package, and whether the function has to reach a private network. A small JavaScript function with no VPC attachment is at the fast end. A large Java application inside a VPC is at the slow end, and those two cases are often discussed as if they were the same thing.

It also matters where the cold start lands. On this site it can only ever land on a form submission, after a human has spent a minute typing. If it landed on every page view, the answer would be different — which is exactly why the pages come out of S3 instead.

If cold starts do matter for your workload

Keep the package small, stay out of the VPC unless you need it, move work out of the module scope and into the handler, and if the path is genuinely latency-critical, use provisioned concurrency and pay for warm containers. That last option is a dial, not a defeat.

06

What it costs

We are not going to print a monthly figure, because yours would be a different number and a made-up one helps nobody. The shape of the bill is the useful part.

A rented server is a rectangle: the same amount every month whether you had a thousand visitors or none. Serverless is a staircase that starts at the floor. For a marketing site with a contact form, the compute side of that staircase barely leaves the floor, because the compute only runs when somebody writes to you.

The honest counter-point: at sustained high volume, a constantly-busy function costs more than a constantly-busy server doing the same work. There is a crossover. Below it you are paying for idle; above it you are paying a premium for not having to operate anything. Knowing roughly where your traffic sits on that line is most of the decision.

How the two cost models compare as traffic grows A server's cost is a flat horizontal line that does not change with traffic. A serverless cost starts near zero and rises with traffic, crossing the server line at a point where sustained traffic becomes high. Below the crossing, serverless is cheaper; above it, the server is. traffic → cost → a rented server — the same every month serverless — follows the work the crossover cheaper here dearer here
No axis numbers on purpose: where the crossover sits depends entirely on your workload, and a drawn number would be a guess dressed as a fact.

07

Deploying it

A push to the main branch starts a GitHub Actions run. The run asks GitHub for a short-lived identity token, hands it to AWS, and AWS gives back temporary credentials for a role scoped to this one job. Then it builds the site and syncs the result to S3.

The point of that middle step is that no AWS access key exists anywhere in GitHub. There is no secret to rotate, no key to leak in a log, and nothing useful for someone who gains read access to the repository.

The deploy pipeline A push to the main branch triggers GitHub Actions, which requests an OIDC token, exchanges it with AWS IAM for temporary credentials, builds the site, and syncs it to the S3 bucket, which serves visitors. git pushto main GitHub Actionsbuilds the site IAM via OIDCno stored keys S3 synctwo passes liveunder a minute
The two-pass sync uploads the fingerprinted assets first and the HTML second, so a page is never live before the files it asks for.

08

When serverless is the wrong answer

We would rather tell you this now than halfway through a migration.

  • Steady, heavy, predictable load. If the work never stops, you are paying a premium for elasticity you are not using.
  • Long-running jobs. A function has a hard execution limit. Work that runs for hours belongs in a container or a batch service.
  • Latency budgets in single-digit milliseconds. Trading paths and similar systems want a warm process you control.
  • Heavy, chatty database work. Many short-lived functions against a traditional relational database need connection pooling you would otherwise not be thinking about.
  • Software you did not write. A licensed application that expects a host is not going to become a function because you would like it to.
  • A hard requirement to stay portable. Serverless ties you to a provider's primitives more tightly than containers do. That can be an acceptable trade — but it should be a decision, not a surprise.

Thinking about this for your own stack?

We build and run this for banks, NBFCs, wealth managers and fintechs — including the parts that cannot be serverless. Tell us what you are running today and we will tell you honestly which parts are worth moving.

Back to case studies