Skip to content

Security & engineering · · 7 min read · Vinny Bonfim

How we rebuilt LimoGrid on AWS: queues, workers, event-driven dispatch

Why LimoGrid's driver offers run on SQS instead of a polling loop, why email and PDFs live in their own worker, and how code reaches production. An engineering story for operators.

LimoGrid runs on AWS: Docker containers, S3 storage behind a CDN, SQS queues in place of RabbitMQ, a Node scheduler in place of legacy cron jobs, and a separate worker that renders PDFs and sends email outside the API. Code ships through CodePipeline and CodeBuild, with CodeQL scanning and weekly Dependabot updates.

The principle behind all of it is simple. Things in a limo company happen because something happened: a driver accepted, a flight moved, a passenger booked. Software should react to those events, not keep checking whether they have occurred yet. That idea shaped the rebuild more than any single AWS service.

I have spent 15 years in software and five running black car operations. This post explains the choices in terms an operator can check against their own day.

What changed when LimoGrid moved to AWS?

ComponentWhat it runs on nowWhy it matters
ApplicationDocker containersOne build, deployed the same way every time
FilesS3 storage and a CDNFiles served close to the user
QueuesSQS, replacing RabbitMQNo message broker for us to run
Scheduled workNode scheduler, replacing cronScheduled jobs live in code
Email and PDFsSeparate worker, S3 outbox, relayHeavy work kept off the API
DeploysCodePipeline and CodeBuildAutomated, repeatable releases
Code checksCodeQL and weekly DependabotFlaws and old libraries caught early

Why does dispatch use a queue instead of polling?

Think about how a job gets covered without software. The dispatcher calls driver one and waits. No answer. Calls driver two. Voicemail. Texts driver three. Meanwhile the phone rings with a new booking, and by the time the dispatcher gets back to the list, nobody remembers whether driver two called back. That is a polling loop, run by a person.

The obvious software version is the same loop, run by a server: check every few seconds whether anyone has accepted, and if enough time has passed, try the next driver. It works in a demo. In production it has three problems. The reaction time can never be better than the polling interval. The server spends most of its effort asking a question whose answer has not changed. And the state of the loop, who has been offered what and when, lives in the memory of one process. Restart that process during a deploy and the loop forgets where it was.

LimoGrid's driver offer queue is event-driven and runs on AWS SQS. Offers go to drivers in sequence. Each driver sees the offer with the pay shown large and a visible countdown. If the driver accepts, the job is assigned. If the countdown runs out, the offer times out automatically and moves on to the next driver. Nobody chases anyone.

The important part is where the state lives. A queued message is stored by the queue service until it is handled, not held in one server's memory, so the offer sequence does not depend on any single process staying up through a mid-shift deploy. The dispatcher sees the result on the dispatch board instead of keeping a mental list.

The same principle runs through flight tracking. We use FlightAware AeroAPI push alerts rather than polling for flight status, so a change arrives as an alert instead of on the next scheduled check. Terminal and gate fill in from 36 hours before pickup, and the driver app shows "terminal TBD" until then. Our post on flight tracking for limo dispatch explains that rule.

Why did we replace RabbitMQ with SQS?

RabbitMQ is a good message broker. But a self-run broker is one more server to patch, monitor, size and recover when something goes wrong at 3am. Every hour spent on the broker is an hour not spent on dispatch.

SQS is a managed service. We send messages and receive them; AWS runs the infrastructure underneath. The trade-off is real: SQS offers less elaborate routing than RabbitMQ, so the flows are built around simple queues rather than complex exchange topologies. For the work a limo platform does, simple queues are the right shape anyway.

Why do email and PDFs run in their own worker?

Rendering an invoice PDF takes real CPU and memory. Talking to a mail server is slow, and the mail server is outside our control. Put both inside the API and they compete with everything else the API does. A month-end invoice run for your corporate accounts should never slow down the passenger trying to book at 11pm.

So LimoGrid runs a separate email worker. PDF rendering and SMTP are isolated from the API. Outgoing messages go into an S3 outbox, and the worker delivers them through a dedicated relay. If a mail server is slow or a batch of PDFs is large, the work waits in the outbox and the worker catches up. The API keeps answering booking and dispatch requests.

Why a scheduler instead of cron jobs?

Legacy cron jobs are lines in a file on a particular server that run a script at a fixed time. They are easy to add and easy to lose. Replace the server and the jobs may go with it. Two runs can overlap. Knowing what ran, and whether it worked, means logging into a machine.

We replaced them with a Node scheduler. The point is that scheduled work becomes part of the application: reviewed, deployed and changed like any other code, instead of living in a crontab on one machine. A limo platform is full of time-based work; driver assignment notifications, for example, can be scheduled for later instead of sent the moment a driver is assigned.

How does code get to production?

Changes are built and deployed through CodePipeline and CodeBuild into Docker containers. Releases are automated, so each one goes out the same way every time.

Two automated checks run on the repositories. CodeQL code scanning looks for common security flaws in our own code. Dependabot opens dependency updates every week, so the open-source libraries under the platform stay current instead of drifting until a published vulnerability forces a rushed upgrade.

The driver app follows the same thinking. It was rebuilt in React and unified with the Backoffice, and updates ship over the air, so a fix reaches drivers without waiting for an app store resubmission. The app version shows on the login screen. More in our post on what a limo driver app should do.

How does tenant isolation work?

Many operators share the same platform, so the most important security property is that one company can never see another's data. The weak way to get there is to rely on every developer remembering to add an ownership check to every new endpoint. Eventually someone forgets.

LimoGrid treats it as a layer instead. Every request is tied to a user session (a JWT) and checked against the company that owns the data. Reservations, accounts, cards and payments have ownership checks. Websocket connections are checked against the company too. The Backoffice uses per-user sessions with no shared tokens, a sliding session timeout, forced expiry, and global revocation. Public booking uses per-company booking tokens, passengers get their own session tokens, and driver login uses one-time-code proofs.

If you are evaluating any vendor, our seven security questions to ask your limo software vendor turns this into a checklist.

What does this mean if you run a limo company?

You should never have to think about queues or containers. What you should notice is this:

  • Driver offers move down the list on their own, with a countdown, even when the office is busy.
  • Invoice PDFs and email run in their own worker, away from booking and dispatch.
  • Flight changes arrive as push alerts, and terminals are not guessed days early.
  • Driver app fixes arrive over the air.
  • Every request against your data is checked against your company.

For investors and acquirers, the short version: the platform runs on AWS, uses a managed queue service instead of a self-run broker, deploys through an automated pipeline, scans its own code, and isolates tenants in a dedicated layer rather than by convention. You can read more about the team on our about page.

Frequently asked questions

Does LimoGrid run on AWS?

Yes. LimoGrid runs in Docker containers on AWS, with S3 storage, a CDN, SQS queues, and deploys through CodePipeline and CodeBuild.

Why does LimoGrid use SQS for driver offers?

So offers move through drivers in sequence and time out automatically, without a person or a polling loop chasing each driver, and without the sequence depending on the memory of a single server.

Can a slow email server affect booking on LimoGrid?

Email and PDF rendering run in a separate worker with an S3 outbox and a dedicated relay, isolated from the API. A slow mail server delays email, not booking.

How does LimoGrid keep its software up to date?

CodeQL scans the code, Dependabot opens dependency updates weekly, and the driver app ships updates over the air.

If you want to see event-driven dispatch on your own trips, book a demo, or get started and send your first offer today.

About the author

Vinny Bonfim — Chief Technology Officer, LimoGrid

Fifteen years in software and five running black car operations — the rare combination where the person designing the dispatch screen has also worked a Friday night on it.

More about the team

See it on your own jobs.

Thirty minutes, your reservations, your rates. If it does not fit how you run, we will tell you so rather than sell you a plan you will cancel in March.