How we built a serverless marketplace for 13p a month

A deep dive into building a fully automated digital marketplace on AWS using Stripe, EventBridge, Lambda, and SES - with no servers, no webhooks, and a monthly infrastructure cost that rounds to zero.

Most marketplace platforms cost thousands a month to run. Ours costs thirteen pence. Not thirteen pounds - thirteen pence. The kind of money you find between sofa cushions.

This is the story of how we built a fully automated digital marketplace on AWS - and why the architecture decisions behind it might change how you think about selling anything online.

The architecture

Marketplace architecture showing the flow from customer browser through CloudFront, Stripe, EventBridge, Lambda, to SES and S3

The marketplace sells PDF guides, consultancy services, training workshops, and software products. The architecture has three layers, and none of them involve a server.

Static site - marketplace pages are plain HTML on S3 behind CloudFront. The “Buy Now” button is a link to Stripe. That is the entire frontend.

Payment - Stripe handles checkout, card processing, and PCI compliance. We never touch payment data.

Delivery - when payment completes, Stripe fires an event through Amazon EventBridge. A Lambda function picks it up, identifies the product, generates a time-limited download link from a private S3 bucket, and emails it to the customer via SES. The whole thing takes under 10 seconds.

Digital delivery flow showing 8 steps from customer click to PDF download

Every component is managed and serverless. Nothing to patch, nothing to scale, nothing to monitor at 3am. The system either works or it does not - and if it does not, the retry mechanisms built into EventBridge and Lambda handle it without intervention.

Why EventBridge instead of webhooks

This is the decision that makes experienced engineers raise an eyebrow - and then nod slowly when they understand why.

The traditional Stripe integration uses webhooks: Stripe POSTs to your endpoint, you verify the signature, parse the payload, handle retries, manage idempotency, and maintain a publicly accessible URL that anyone on the internet can probe. That means API Gateway, authentication, and the inevitable “why is my webhook returning 502” debugging session.

Stripe has a native EventBridge integration. Instead of webhooks, Stripe publishes events directly to a partner event bus in your AWS account. The Terraform is three resources:

resource "aws_cloudwatch_event_bus" "stripe" {
  name              = var.stripe_event_source
  event_source_name = var.stripe_event_source
}

resource "aws_cloudwatch_event_rule" "checkout_completed" {
  name           = "stripe-checkout-completed"
  event_bus_name = aws_cloudwatch_event_bus.stripe.name

  event_pattern = jsonencode({
    detail-type = ["checkout.session.completed"]
  })
}

resource "aws_cloudwatch_event_target" "delivery_lambda" {
  rule           = aws_cloudwatch_event_rule.checkout_completed.name
  event_bus_name = aws_cloudwatch_event_bus.stripe.name
  arn            = aws_lambda_function.delivery.arn
}

No API Gateway. No public endpoint. No webhook signature verification. Events arrive through AWS’s internal infrastructure, so the attack surface for the payment processing flow is zero. You configure it in the Stripe dashboard by pasting your AWS account ID and region - Stripe creates the partner event source, you associate it in Terraform, done.

The thin event catch

EventBridge receives “thin events” from Stripe - the event type and object ID, but not the full payload. A checkout.session.completed event does not include line items or customer details. The Lambda needs to call the Stripe API to fetch the full session:

url = f"https://api.stripe.com/v1/checkout/sessions/{session_id}?expand[]=line_items"
req = Request(url)
credentials = base64.b64encode(f"{stripe_key}:".encode()).decode()
req.add_header("Authorization", f"Basic {credentials}")

with urlopen(req) as resp:
    full_session = json.loads(resp.read().decode())

This is actually a security feature - you always work with authoritative data from Stripe rather than a potentially stale event payload. The API key lives in SSM Parameter Store as a SecureString, fetched at runtime and cached in memory. It never appears in environment variables, code, or Terraform state.

Four delivery flows, one Lambda

The handler routes products to different email flows based on type. A dictionary maps Stripe product IDs to S3 keys and delivery logic:

Product TypeWhat Happens After Payment
Digital downloadPresigned S3 URL emailed (24hr expiry)
ConsultationIntake form PDF emailed for pre-session preparation
Training workshopSetup checklist emailed with software to install before the day
Software licenceDeployment guide + source code download link emailed
BundleMultiple flows triggered from a single checkout

Each flow has a branded HTML email template with inline styles, a plain text fallback, and a clear call-to-action. The emails look professional because email clients in 2026 still cannot handle a <link> tag, so every style is inline. We are not bitter about this at all.

Bundle purchases iterate through component products and trigger the appropriate flow for each. One payment, multiple deliveries, zero manual intervention.

Security by absence

Security layers showing concentric protection from CloudFront through to SSM Parameter Store

The security architecture is defined as much by what we did not include as by what we did.

What is there: CloudFront with HSTS, CSP, and security headers. Private S3 buckets with all four public access blocks. Lambda IAM scoped to specific resources - no wildcards, no *. SSM for secrets at runtime. SES with verified sender identity.

What is not there - and why:

ServiceWhy We Skipped It
AWS WAFNo API endpoint to protect. Static HTML does not need a web application firewall.
CognitoNo user accounts. Pay, receive product, done.
DynamoDBNo state. Stripe is the order ledger. S3 is the product store. Lambda is stateless.
API GatewayNo API. EventBridge replaced the webhook endpoint entirely.

Every service we excluded is a service we do not need to secure, monitor, patch, or pay for. The best security control is the one you do not need because the attack surface does not exist.

What it costs

Monthly cost breakdown showing total infrastructure cost of $0.13

ServiceMonthly CostNotes
S3 (website + products)~$0.03Two buckets, minimal storage
CloudFront$0.00Free tier: 1TB/month
Lambda$0.00Free tier: 1M requests/month
EventBridge$0.00Partner events are free
SES~$0.10Per 1,000 emails
SSM / CloudWatch~$0.00Standard tier, minimal logs
Total~$0.13/month

For context: a t3.micro costs $8.50/month. A NAT Gateway costs $35/month. An ALB costs $18/month. We are running an e-commerce platform for less than the cost of a CloudWatch dashboard.

Stripe charges 2.9% + 20p per transaction, but that scales with revenue, not time. When nobody is buying, the infrastructure cost is thirteen pence. When sales spike, the per-transaction fees are covered by the revenue they generate. This is the serverless promise actually delivered.

The Terraform

The entire infrastructure is a single Terraform module:

ResourcePurpose
S3 bucket + versioning + encryption + public access block + lifecyclePrivate product storage with 30-day version expiry
SES identityVerified sender email
IAM role + policyLeast-privilege: specific bucket, specific SES identity, specific SSM param, specific log group
CloudWatch log group30-day retention
Lambda functionPython 3.12, 128MB, 30s timeout
EventBridge bus + rule + targetStripe partner source, checkout filter, Lambda invocation
Lambda permissionAllow EventBridge to invoke

State in S3 with DynamoDB locking. Destroy and recreate from scratch in under 2 minutes.

Digital products under UK consumer law have a specific wrinkle. The Consumer Contracts Regulations 2013 give buyers a 14-day cooling-off period - unless they consent to immediate delivery of digital content, which waives that right. We enforce this at three levels:

  1. Terms of Service - section 4a cites the regulation and states digital downloads are non-refundable once the link is issued
  2. Product pages - a visible notice above every Buy Now button
  3. Delivery mechanism - the link is issued automatically within seconds, constituting the delivery that triggers the waiver

Legal framework, user experience, and technical implementation aligned. Not because we are difficult - because we are thorough.

Well-Architected assessment

We designed this marketplace against the six pillars of the AWS Well-Architected Framework. Here is how each pillar maps to the architecture:

Operational excellence

Everything is in Terraform. The Lambda handler is versioned in Git. Deployments are a single terraform apply. CloudWatch logs have 30-day retention with structured logging that includes redacted customer emails and product names - enough to debug, not enough to leak PII. Adding a new product is a 15-minute checklist, not a project.

The operational burden is effectively zero. There are no servers to patch, no AMIs to rotate, no scaling policies to tune. The only operational task is monitoring the CloudWatch logs after a new product launch to confirm the delivery flow works - and even that is optional because the architecture is identical for every product.

Security

Covered in detail above, but the Well-Architected framing is worth stating explicitly:

  • Identity and access management - Lambda IAM role scoped to specific ARNs. No wildcards. SSM parameter access restricted to the single key the Lambda needs.
  • Detection - GuardDuty enabled at the account level (managed in a separate landing zone repo). CloudTrail logging all API activity.
  • Infrastructure protection - no public endpoints. S3 buckets private with all four access blocks. CloudFront with TLS 1.2 minimum and security headers.
  • Data protection - S3 encryption at rest (AES-256). Presigned URLs for time-limited access. Stripe API key in SSM SecureString, fetched at runtime.
  • Incident response - CloudWatch alarms on Lambda errors. SES bounce handling via SNS. The irony of selling an incident response playbook while having an architecture simple enough to not need one is not lost on us.

Reliability

The architecture has no single points of failure that we control. CloudFront is globally distributed. S3 offers 11 nines of durability. Lambda runs across multiple Availability Zones automatically. EventBridge is a regional service with built-in redundancy. SES has automatic retry with exponential backoff.

The only external dependency is Stripe, and if Stripe is down, nobody is completing a checkout anyway - so the delivery Lambda simply has nothing to do. There is no failure mode where a customer pays but does not receive their product, because the payment and the delivery trigger are the same event. If the event does not arrive, the payment did not complete.

If the Lambda fails (bad code, SSM timeout, SES throttle), EventBridge retries with exponential backoff for up to 24 hours. The customer might wait a few minutes instead of a few seconds, but they will get their email.

Performance efficiency

Lambda is configured at 128MB - the minimum. The handler fetches a session from Stripe, generates a presigned URL, and sends an email. It does not need more memory. Cold starts are under 600ms (Python 3.12 with no external dependencies beyond boto3 and urllib). Warm invocations complete in under 300ms.

We deliberately avoided over-engineering the compute layer. There is no container, no Fargate task, no ECS service. The Lambda runs for less than a second per invocation and then disappears. The cost per invocation is approximately $0.0000002. We would need to process roughly 5 million purchases before the Lambda compute cost exceeds $1.

Cost optimisation

Covered in the cost section above, but the Well-Architected principle worth highlighting is pay only for what you consume. Every service in this architecture is priced per-request or within the free tier. When nobody is buying, the cost is thirteen pence. When sales spike, the marginal cost per transaction is fractions of a penny. There is no baseline cost that runs whether or not the marketplace is generating revenue.

This is the fundamental difference between serverless and traditional architectures. A t3.micro running 24/7 costs $8.50/month whether it processes zero transactions or ten thousand. Our Lambda costs nothing until someone buys something.

Sustainability

Minimal compute footprint. Lambda runs for under a second per transaction and then releases all resources. No idle servers consuming energy. CloudFront serves cached content from edge locations, reducing data transfer. S3 lifecycle policies expire old object versions after 30 days, preventing unbounded storage growth.

The sustainability pillar is often treated as an afterthought, but serverless architectures are inherently more sustainable than always-on alternatives. Shared infrastructure, high utilisation rates, and pay-per-use pricing align economic incentives with environmental ones.

How we actually built it

This marketplace was built using Kiro, an AI-powered IDE, with Stripe’s MCP server integration for product management. The workflow looked like this:

  1. Designed the Lambda handler and Terraform module
  2. Created Stripe products, prices, and payment links via MCP tools - directly from the IDE, no dashboard clicking
  3. Built the marketplace pages with consistent sidebar navigation
  4. Deployed the Lambda via terraform apply
  5. Uploaded assets to S3, pages to the website bucket, invalidated CloudFront
  6. Tested with a real purchase, found the thin event bug, fixed it, redeployed

The Kiro steering file (.kiro/steering/project.md) carries persistent context across every conversation - AWS account IDs, bucket names, deployment conventions, British English rules, the lot. Without it, every interaction starts from “what is the S3 bucket name?” With it, the AI knows the entire project context and can make changes across multiple files consistently.

The Stripe MCP integration was particularly effective. Creating a product, setting a price, and generating a payment link is three tool calls. The limitation - MCP tools cannot set after_completion redirect URLs - was the only point where we dropped to curl.

The PDF delivery pipeline

The digital products are HTML documents with a custom design system - branded cover pages, callout boxes, code blocks, diagrams, tables, and checklists. Chrome headless converts them to PDF for delivery:

chrome --headless=new --no-pdf-header-footer --print-to-pdf=output.pdf source.html

The HTML source files are version-controlled. The PDFs are generated artefacts. If we update a guide, we regenerate the PDF, upload it to S3, and the next customer who purchases gets the updated version automatically. No deployment required - just an S3 upload.

The design system ensures every guide looks consistent: Inter font, dark gradient cover, blue accent headings, green tip boxes, orange warning boxes, branded footer. One brand, one system, zero design drift.

Lessons learned the hard way

Thin events are not obvious. Our first live purchase failed silently. The Lambda received the event, found no line items, returned 200. We had to add the Stripe API fetch. It is documented, but easy to miss if you are used to webhook payloads.

Restricted keys are restricted. The initial SSM parameter had a restricted key (rk_live_*). It could create products but returned 401 when reading checkout sessions. The Lambda needs a full secret key (sk_live_*). Twenty seconds of debugging, one character prefix.

Chrome headless leaks file paths. The old flags (--headless --print-to-pdf-no-header) embed the local file path in the PDF footer. Customers would see /Users/martyn/gitrepos/... on every page. The fix: --headless=new --no-pdf-header-footer. Small detail, significant when you are selling the output.

Cache your SSM lookups. Without caching, every Lambda invocation calls SSM. A module-level variable that persists across warm invocations reduces SSM calls by 90%+. Cold starts fetch; warm invocations use the cache.

MCP tools have limits. Stripe’s MCP integration created 15 products with prices and payment links in minutes. But it cannot set after_completion redirect URLs on payment links. Know your tools before you commit to a workflow.

Coming soon: the full catalogue

The PDF guides are live now. The same Lambda, the same EventBridge integration, and the same SES pipeline will power everything else when it launches.

Consultancy

ProductWhat You Get
1-Hour Cloud ConsultationFocused session with a senior cloud engineer. Architecture, migration, cost, security - whatever you need, distilled into one actionable hour.
Well-Architected ReviewYour AWS environment reviewed against all six pillars. Prioritised findings with specific remediation steps, not a generic checklist.
Migration Assessment7-day deep dive. Workload analysis, dependency mapping, cost projections, and a phased roadmap for up to 10 applications. A plan you can execute, not a deck you file away.

Training workshops

WorkshopDurationWhat You Walk Away With
AWS FundamentalsHalf dayConfidence with core services, IAM, networking, and cost management
Terraform BootcampFull dayReal infrastructure deployed. HCL, state, modules, CI/CD - hands on keyboard
Git for OperationsFull dayBranching, merging, and team workflows for ops teams who have been avoiding Git
DevOps and CI/CDFull dayProduction pipelines. Docker, testing, deployment strategies that actually work
Cloud Security EssentialsHalf dayIAM, network security, encryption, and incident response - the practical bits
AI for Business LeadersHalf dayReal use cases, real costs, real risks. No hype, no buzzword bingo

Software

ProductWhat It Does
AWS Migration TrackerFull-stack tracking for AWS migrations. Dashboards, Grafana analytics, task templates. Built for MAP engagements.
RACI Matrix ManagerResponsibility assignments without the spreadsheet. Because every organisation needs a RACI and nobody wants to maintain one in Excel.

Bundles

BundleComponentsThe Logic
Migration Assessment + Tracker7-day assessment + tracking platformPlan the migration, then track execution. One package, one price.
Terraform + DevOps 2-DayTerraform Bootcamp + DevOps WorkshopDay one: build the infrastructure. Day two: automate the deployment.

Adding a new product to any category takes about 15 minutes: create the Stripe product, add the routing entry, upload the deliverable, create the marketplace page, deploy. The architecture does not care what you are selling - it cares about the event and the delivery flow.

What happens when it scales

The question every architect asks: “what happens when traffic grows?” Here is the answer:

Monthly SalesRevenueStripe FeesAWS InfrastructureProfit Margin
10£13.00~£2.90~$0.1377%
100£130.00~£29.00~$0.2377%
1,000£1,300.00~£290.00~$1.3077%
10,000£13,000.00~£2,900.00~$4.5077%

At 10,000 sales per month, the infrastructure cost is $4.50. The revenue is £13,000. The ratio of infrastructure cost to revenue is 0.03%. The Lambda would use roughly 1% of its free tier allocation. EventBridge would still be free. SES would cost about $1 for the emails. The S3 presigned URL generation adds nothing measurable.

The profit margin stays flat at 77% regardless of volume because Stripe’s percentage fee dominates the cost structure. The infrastructure cost is so small it is a rounding error at every scale. There is no point at which you need to “upgrade” to a bigger server, add a load balancer, or provision a database. The architecture is the same at 10 sales as it is at 10,000.

This is not theoretical. Every component has been tested against AWS’s published limits. Lambda supports 1,000 concurrent executions by default. EventBridge can handle thousands of events per second. SES can send 200 emails per second in production mode. The ceiling is so far above any realistic sales volume that capacity planning is not a conversation worth having.

Disaster recovery in 5 minutes

The entire marketplace can be rebuilt from scratch in under 5 minutes. Not restored from a backup. Not recovered from a snapshot. Rebuilt from nothing.

Here is the recovery plan:

  1. terraform apply - provisions the S3 bucket, Lambda, EventBridge, SES identity, IAM roles, and CloudWatch log group (~2 minutes)
  2. Upload PDFs to S3 - aws s3 sync the product files to the delivery bucket (~30 seconds)
  3. Deploy marketplace pages - aws s3 sync the HTML to the website bucket, invalidate CloudFront (~1 minute)

That is it. There is no database to restore. There is no application state to recover. There are no snapshots to find in a backup vault. The Git repository is the disaster recovery plan. Terraform state is in S3 with versioning. The product files are in a versioned S3 bucket. The Lambda code is zipped from the repo at apply time.

The order history lives in Stripe. The subscriber list lives in DynamoDB with point-in-time recovery. The product files are versioned in S3. Every piece of state that matters is either in a managed service with its own durability guarantees or in version control.

Compare this to a traditional e-commerce platform where disaster recovery means restoring a database from a point-in-time snapshot, redeploying application servers, warming caches, verifying data consistency, and hoping the backup was not corrupted. We have seen DR tests take days. Ours takes less time than making a cup of tea.

How this compares to the alternatives

We could have used an off-the-shelf platform. Here is what that would have cost:

PlatformMonthly CostTransaction FeesWhat You GetWhat You Give Up
Shopify Basic$39/month2.9% + 30cHosted storefront, payment processing, basic analyticsControl, customisation, data ownership
WooCommerce$20-50/month (hosting)Payment gateway feesSelf-hosted, full control, plugin ecosystemMaintenance burden, security patching, scaling headaches
Gumroad$0/month10% of revenueSimple digital sales, hosted checkout10% of every sale, limited branding, platform dependency
Lemon Squeezy$0/month5% + 50cDigital sales, tax handling, hosted checkout5% of every sale, limited customisation
This architecture$0.13/month2.9% + 20p (Stripe)Full control, custom branding, automated delivery, own your dataYou have to build it

Shopify costs $39/month whether you sell anything or not. At zero sales, that is $39 of pure overhead. Our architecture costs 13 cents. At 100 sales per month on Gumroad, you would pay $13 in platform fees. On our architecture, the infrastructure cost for those 100 sales is 23 cents.

The trade-off is build time. We had to write the Lambda, the Terraform, the email templates, and the marketplace pages. That is a one-time investment. The ongoing cost advantage compounds every month. After 12 months, Shopify has cost $468 in platform fees alone. Our infrastructure has cost $1.56.

The other trade-off is maintenance. Shopify handles updates, security patches, and scaling for you. We handle nothing, because there is nothing to handle. Lambda is patched by AWS. S3 is managed by AWS. EventBridge is managed by AWS. The only code we maintain is the Lambda handler - 650 lines of Python that routes products to email templates. It changes when we add a product, which is a 2-line dictionary update.

For a consultancy selling digital products, the economics are not close.

The takeaway

You do not need a platform team, a microservices architecture, or a six-figure budget to sell products online. Stripe for payments, EventBridge for events, Lambda for logic, SES for email, S3 for storage. Thirteen pence a month.

The hardest part was not the architecture. It was getting Chrome to stop printing the local file path in the PDF footer.

That is dysfunctional lazy engineering at its finest.

One more thing: the subscribe button

You might have noticed the subscribe form at the bottom of this page. That is also serverless, also Terraform, and also costs essentially nothing.

The architecture: API Gateway (HTTP API) receives the POST from the blog, invokes a Lambda that writes to DynamoDB, sends a double opt-in confirmation email via SES, and waits for the click. Confirmed subscribers get new posts delivered to their inbox. The Lambda handles subscribe, confirm, and unsubscribe from a single function, routing on the API path.

The interesting bits:

  • Double opt-in - every subscription requires email confirmation. No confirmed click, no emails. This is a GDPR requirement and also prevents abuse.
  • Suppression list - a separate DynamoDB table tracks emails that must never be contacted again (bounces, complaints). The send Lambda checks suppression before every email. Records never expire.
  • Bounce and complaint handling - SES publishes bounce and complaint notifications to SNS topics, which invoke the Lambda to automatically suppress the offending address. If someone marks us as spam, they are immediately and permanently removed. No manual intervention.
  • Rate limiting - API Gateway throttles at 5 requests per second with a burst of 10. Enough for legitimate use, not enough for abuse.
  • DynamoDB on-demand - pay-per-request billing. Zero cost when nobody is subscribing. Point-in-time recovery enabled for data protection.
  • CORS locked down - the API only accepts requests from kaizenconsultancy.io. No cross-origin abuse.

The newsletter send Lambda fetches the blog RSS feed, identifies new posts, scans the subscribers table, checks each address against the suppression list, and sends personalised emails with an unsubscribe link unique to each subscriber. One Lambda invocation, one DynamoDB scan, one SES call per subscriber.

Total additional infrastructure cost: effectively zero. DynamoDB on-demand with a handful of records costs nothing measurable. API Gateway’s free tier covers 1 million requests per month. The Lambda runs for a few seconds per newsletter send.

The subscribe form below this post is the proof. Try it - you will get a confirmation email within seconds, and every new blog post will land in your inbox. If you do not like it, the unsubscribe link is one click and permanent.

We built the marketplace delivery pipeline, the mailing list, and the blog on the same principles: event-driven, serverless, Terraform-managed, and priced per-request. The total infrastructure cost for all of it - marketplace, mailing list, blog, and the main website - is under $2 per month.

Two pounds. For everything.

Dysfunctional lazy engineering. We will keep saying it until it stops being true.

Get notified of new posts

Enter your email to receive blog updates. No spam, no marketing - just new posts about cloud engineering and DevOps. Unsubscribe anytime.