AWS Builder Center
Introducing Sahara: A Business OS Built on WhatsApp

Introducing Sahara: A Business OS Built on WhatsApp

My mother runs a small store in Nepal. My uncle runs a small hotel a few hours away. Neither of them has ever opened an app for their business, not once, and they never will. Everything happens over WhatsApp, the one thing they already know how to use. So that's what I built Sahara on top of: three AWS databases doing real work behind a single phone number, with nothing new for either of them to learn.

The problem nobody is building for

My mother runs a small store in Nepal. My uncle runs a small hotel a few hours away. Two completely different businesses. One shared tool: WhatsApp.
Not a point-of-sale system. Not a booking platform. Most days, not even a spreadsheet. Just WhatsApp, a notebook, and memory.
That's not unusual, either. It's the default for most small businesses on earth. Somewhere around 70 million of them run this way: too small for enterprise software, too informal for most SaaS signup flows, and still pulling in real revenue every day.
When I actually sat down and mapped out what my mother's store needed against what my uncle's hotel needed, something clicked that ended up shaping the whole architecture of Sahara. These aren't the same data problem in different clothes. They're genuinely different problems. Different performance needs, different consistency needs, different analytical needs.
I could have forced both into one database. It's simpler, and it's what most early products do. I didn't, because it would have meant compromising on all three.

Three problems, not one

Before I opened the AWS console, I broke down what each business actually needed from its data layer.
My mother's store needs speed. When she texts "sold 2 coke," that update has to land in well under a second, because she's standing at the counter with a customer waiting, not refreshing a dashboard later. Inventory gets read and written constantly through the day. None of it needs to live forever in raw form either. A stale "checked in" flag or an old stock snapshot should just expire and go away on its own.
My uncle's hotel needs consistency. A booking, a guest profile, a price change, these are financial records. If a guest gets double-booked because two writes landed in the wrong order, that's not a UX bug. That's real money, gone wrong. And his bookings don't all come from one place. Guests and agents hit the system from different regions, so it needs to stay consistent across all of them.
Both businesses need intelligence, just for different reasons. Neither of them has time to study a sales chart. They need the system to notice things on its own, like stock running low before a weekend rush or bookings spiking with the season, and just tell them. As a WhatsApp message. Not a dashboard they have to remember exists.
So that's three very different access patterns, and Sahara ended up with three purpose-built databases to match. Not because more databases looks impressive in a writeup. Because each one is actually the right tool for its job.

The architecture

At a high level, every message from a business owner follows one path:
1
2
3
4
5
6
7
8
9
WhatsApp (via OpenWA)
│
▼
Next.js API Routes (hosted on Vercel)
│
├──► DynamoDB (live inventory, active check-ins)
├──► Aurora DSQL (customer profiles, booking history, pricing)
└──► Aurora PostgreSQL (sales trends, demand forecasting via pgvector)
+ pgvector
OpenWA is the WhatsApp gateway. Self-hosted, free, which matters when your customer can't absorb a $500 a month messaging bill before they've made a single sale through the system. A message comes in, gets parsed, and lands on a Next.js API route based on intent: "sold," "bookings," "help," whatever it is.
From there the route talks to whichever database owns that part of the business logic. This is the part worth walking through properly, because the why behind each choice matters more than the what.

DynamoDB: the database for right now

Inventory and active check-ins are the most write-heavy, latency-sensitive part of Sahara. When my mother texts "sold 2 coke," there's no room for a slow round trip.
DynamoDB handles this for two reasons.
First, the latency. A key-value lookup against a partition key, store ID plus item SKU, comes back in well under 5ms. No query planner overhead, no joins, just a direct hit on the data that needs to change.
Second, TTL. Hotel check-ins are a good example of data that's only useful for a window. Once a guest checks in, that "active" state doesn't need to live forever. It needs to expire on its own after 24 hours, so nothing piles up that someone has to go clean by hand later. DynamoDB's TTL attribute does this with zero cron jobs and zero background workers. Set an expiry timestamp on the item, and DynamoDB takes care of the rest.
Fast writes, fast reads, automatic expiry. That's what live data actually needs, and it's a clunky fit for a relational database where TTL has to be bolted on after the fact.

Aurora DSQL: the database for this has to be correct

Customer profiles, booking history, and pricing live in Aurora DSQL, Amazon's distributed SQL database. This is the layer where being correct matters more than being fast.
A booking record isn't just a row in a table. It's a commitment. If my uncle's hotel has one room left and two bookings come in close together, the database has to guarantee only one of them wins. Full stop. That's distributed ACID transactions doing their job, not eventual consistency shrugging and hoping for the best.
There's a second reason Aurora DSQL is here, and it's easy to miss until you actually have customers spread out: global consistency without babysitting replication yourself. Sahara's customers aren't all sitting in Pokhara. Bookings come in from guests and agents wherever they happen to be. Aurora DSQL keeps things strongly consistent across regions without me hand-rolling a replication and conflict-resolution layer, which is not something a two-person hackathon team has any business building from scratch.
Pricing logic lives here too, for the same reason. A guest should never see two different prices for the same room because two parts of the system quietly disagreed about what was true.

Aurora PostgreSQL + pgvector: the database that thinks ahead

The third database isn't something my mother or uncle ever talk to directly. It sits in the background, watching patterns across everything the other two databases generate.
Aurora PostgreSQL with the pgvector extension stores embeddings built from sales history, seasonal patterns, demand signals. Once enough data builds up, say a few weeks of "sold 2 coke" messages, the system can run similarity search against past patterns and answer something nobody actually asked: is this item about to run out before the next restock?
This is the piece that turns Sahara from "a way to text your inventory updates" into something that tells a business owner, unprompted, you're about to run low on rice, here's when this happened last time. pgvector makes that kind of forecasting possible inside an ordinary PostgreSQL instance. No separate vector database to stand up and maintain. Just an extension on a database AWS already manages.
I kept this analytical workload in its own database on purpose. Mixing heavy analytical queries into the same database handling live inventory writes is one of the fastest ways to make a system crawl under load. Keeping it separate means my mother's "sold 2 coke" message never sits behind a similarity search.

Why Next.js API routes tie it together

Sahara is one Next.js app, deployed on Vercel. Frontend dashboard and backend API routes, same codebase. Nothing separate to deploy, version, or keep in sync.
Each API route does one job and talks to exactly the database that owns that data:
1
2
3
/api/inventory/update → DynamoDB
/api/bookings/create → Aurora DSQL
/api/insights/forecast → Aurora PostgreSQL + pgvector
That keeps the routing dead simple. OpenWA hands off a parsed WhatsApp message, the API layer figures out what it's asking for, and the matching route talks to the matching database. The dashboard reads from the same routes too, so when my mother texts "sold 2 coke" from her phone, the web dashboard updates in real time. Same source of truth, two doors into it.
Running it all through Vercel's edge network also keeps the API routes close to wherever the request is actually coming from, which matters a lot when your users are spread across regions and the one requirement that can't slip is "this has to feel instant on a basic Android phone."

What this architecture actually buys us

It would've been faster to throw everything into one Postgres instance and call it a day. For a hackathon, that's a fair shortcut to take. I didn't take it, because the businesses I'm actually building this for, and the 70 million like them, don't have room for a system that slows down under load, gets pricing wrong under concurrency, or has nothing useful to say about their own sales.
Three databases, each doing one job well, tied together through a single Next.js codebase. Not three because more sounds impressive. Three because inventory, bookings, and insight are three different problems, and treating them like one is how systems end up breaking in production.
My mother doesn't know what DynamoDB is. She doesn't need to. She just needs "sold 2 coke" to work, every single time, in under a second. That's what all of this is actually for.

Sahara is being built for the Vercel + AWS Databases Hackathon. Next up: how OpenWA turns a WhatsApp number into a full business interface with zero app installs.
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