
DevOps for SaaS Companies: Complete Setup Guide
A practical guide to production-grade DevOps for SaaS: CI/CD phases, infrastructure as code, actionable monitoring, SLOs, secrets, and automated rollback.

14 min read
Key Takeaways
A cash-on-delivery ecommerce platform takes an order without taking a payment, confirms that the customer still wants it, hands the parcel to a courier who collects the cash at the door, and reconciles that cash back to the order — and it must do this across at least five external systems, from the SMS gateway to the courier's status feed, each of which can independently become the point where an order is lost.
The foundational choices — how a customer is identified, when an order counts as confirmed, which system owns the parcel's status, and how a message failure is handled — determine how many parcels come back unopened and how many hours staff spend re-typing addresses into a courier portal.
This guide covers the reference design a team needs to build cash on delivery as a genuine operating model, not as a payment option bolted onto a card-first store.
A cash-on-delivery ecommerce platform is a commerce system in which the order is created before any money moves, payment is collected by a courier at the moment of delivery, and the software is responsible for every step in between: confirming intent, assessing risk, booking the parcel, tracking it, and recording the cash when it arrives.
The definition matters because it moves the centre of the system. In a card-first store the checkout is the climax; everything afterwards is fulfilment. In a cash-on-delivery store the checkout is the opening move. The customer has committed nothing, the retailer is about to spend a courier fee in both directions if the parcel is refused, and the commercially important software is the screen a staff member looks at ten minutes later.
Markets where this model dominates — Bangladesh, and much of South and Southeast Asia, the Middle East and Africa — share three further traits that shape the build: customers identify themselves by mobile number, they read SMS and messaging apps rather than e-mail, and delivery is priced and operated by local couriers with their own APIs and conventions.
Hosted commerce platforms are engineered for the median requirements of their customer base, and that median customer pays by card, has an e-mail address and ships with an international carrier. Each local difference can usually be patched with an app, but every app adds a recurring fee, a separate interface and its own copy of the order data — and the patches do not compose into a workflow.
| Assumption in a card-first store | Reality in a cash-on-delivery market | What the platform must provide |
|---|---|---|
| Payment completes the order | Payment happens days later, at the door | An explicit confirmation state between "placed" and "shipped" |
| E-mail is the customer's identity | The mobile number is | Phone-based accounts, one-time codes, guest orders with no e-mail |
| E-mail receipts and updates | SMS is what gets read | Short, local-language SMS at every step |
| One shipping rate table | Delivery is priced by city zone | Zone-based fees and a free-delivery threshold as configuration |
| Fulfilment is a label purchase | Fulfilment is a booking with a local courier | Courier booking, tracking and status sync inside the order screen |
| Fraud means stolen cards | Fraud means fake or refused orders | A risk score per phone number before the parcel ships |
The practical test is simple. If staff confirm orders in one tool, book couriers in a second and message customers from a third, the business is running its core workflow in the gaps between products. That workflow is the thing worth building.
A headless design suits this problem because the storefront and the back-office workflow change at different speeds and for different reasons. A reference architecture includes the following components.
Resist the instinct to distribute this across managed services on day one. A single-vendor store serving one country runs comfortably on one well-provisioned server with containers, a reverse proxy and automated backups. Record, in the code, every shortcut that depends on the single-server assumption — in-memory rate limiting is the usual example — along with the change required when the store scales out.
Treat the mobile number as the primary key of the customer and design every entry point around it.
Normalise at the boundary. Customers type the same number in several forms — with a country code, with a plus sign, with spaces. Convert every number to one canonical form the moment it enters the system, store only that form, and match the common variants when querying older data.
// One canonical form: 01XXXXXXXXX
normalizePhone("+880 1712-345678") // "01712345678"
normalizePhone("8801712345678") // "01712345678"
normalizePhone("1712345678") // "01712345678"
normalizePhone("12345") // null — reject, do not guess
Authenticate with one-time codes. Sign-up, sign-in and password reset should work from a code sent by SMS. Rate limit the request endpoint tightly, per IP address and per number, because each request costs money and an open endpoint is an invitation to abuse.
Make guests first-class. A customer who orders without an account and without an e-mail address is the normal case, not an edge case. If the underlying commerce engine requires an e-mail, generate an internal placeholder, never send mail to it, and never show it in the interface.
Believe the client's address carefully. Rate limits are only as good as the IP address they key on. Trust a forwarded-address header only when the request arrives from your own proxy; otherwise anyone can reset their own limit by changing a header.

Model the stages the business actually experiences rather than the two or three a generic order status offers. A workable lifecycle has five states.
| Stage | Meaning | What moves it forward |
|---|---|---|
| Placed | Order received, nothing verified | Customer taps a confirmation link, or staff confirm by phone |
| Confirmed | Customer intent verified | Staff pack the items and book a courier |
| With courier | Parcel booked and handed over | Courier reports delivery |
| Delivered | Parcel accepted, cash collected | Payment is recorded against the order |
| Cancelled | Refused, unreachable or returned | — |
Keep the state small and explicit, and attach it to the order so that every screen and every job reads the same truth.
{
"confirmation": "confirmed",
"confirmed_by": "customer",
"payment": "cod",
"courier": {
"name": "steadfast",
"consignment_id": "91827364",
"tracking_code": "SF123456",
"status": "in_transit"
}
}
Two implementation rules prevent subtle damage. Re-read the order immediately before writing to this state, because a webhook, a scheduled job and a staff member can all update the same order within the same second. And design the operations screen around the next action, not the record: a staff member opening an order should see which stage it is in and the one thing to do now, with the full detail folded away beneath it.
A refused parcel costs the outbound courier fee, the return fee, the packaging and a week of unavailable stock. Two controls do most of the work.
Confirm before shipping. When an order is placed, send an SMS containing a signed link that confirms the order in one tap. The signature — an HMAC of the order identifier with a server-side secret — means the link cannot be forged or altered to confirm someone else's order. Orders that are not confirmed by link go to a call queue. The link removes the easy calls from that queue, leaving staff to spend their time on the orders that need a conversation.
Score the phone number. Before a parcel is booked, show staff a simple score built from evidence the business already holds.
| Signal | Effect on the score |
|---|---|
| Earlier orders from this number that were delivered | Raises trust |
| Earlier orders from this number that were cancelled | Lowers trust |
| The courier's own acceptance record for this number, where the courier exposes one | Blended with the store's history |
| No history anywhere | Flagged as a new customer, not as high risk |
| Number fails validation | Treated as high risk |
Keep the score explainable. A label of "medium risk" is less useful than "one delivered order, two cancelled, courier record 4 of 9 parcels accepted" — staff act on reasons, not on numbers.
Courier APIs are simple to call and easy to integrate badly. The failures cluster in four places.
Order the booking steps so that nothing is lost. Booking a parcel involves an external call that cannot be undone, followed by several internal writes. Persist the courier's consignment number the instant it is returned, before creating shipments, sending SMS or updating anything else. If a later step fails, the order still knows its parcel exists and a retry does not book a second one.
Make booking idempotent. Send a stable invoice reference derived from the order number, and refuse to book an order that already carries a consignment. A double click should never produce two parcels.
Map statuses deliberately. Each courier reports dozens of raw statuses; reduce them to the handful the business cares about and decide what each one means for money. A partial delivery is not a delivery. An error or an unrecognised status is not "in transit" — treat it as unknown and leave the previous state untouched.
Combine webhooks with polling. Accept the courier's status webhook where one exists, authenticate it with a shared token, and also poll open parcels on a schedule. Webhooks are fast and occasionally missing; polling is slow and reliable. Put a timeout on every outbound call so that a slow courier API cannot stall the order screen.
Finally, treat returned parcels as work, not as an ending. A parcel the courier marks as returned should surface in the operations queue until someone has restocked it and closed the order.
Customers who prefer to pay in advance will use a local wallet or card gateway, and that payment is a round trip: the customer leaves the store, pays on the gateway's page and is redirected back.
Do not trust the redirect. A browser can be closed mid-journey, a return URL can be replayed, and a query string that says "success" proves nothing. When the customer returns, the server must ask the gateway directly whether that transaction was paid, for that amount and in that currency, and complete the order only on that answer.
Where the gateway offers a server-to-server payment notification, handle it as an independent path to completing the order, so that a customer who pays and then loses their connection still has an order. Because two paths can now complete the same order, make completion idempotent. One smaller detail catches many teams: the session cookie must survive the cross-site redirect back from the gateway, which means its same-site policy has to allow top-level navigation.
SMS is the primary customer channel, a direct cost and a regulated medium, and it deserves the engineering attention e-mail usually receives.
The overwhelming share of traffic in these markets arrives on a phone, often a mid-range Android device on a variable connection, and the storefront should be designed for that device first rather than adapted to it.
Build the interaction model customers already know from native apps: a bottom tab bar, sheets that rise from the bottom edge, a visible pressed state on everything tappable, and a header that steps out of the way on scroll. Self-host the fonts — particularly the local-script typeface — so that the first render never waits on a third-party server. Set prices in the local currency in the form customers expect, and test every page at a narrow phone width for horizontal overflow.
Two additions extend the reach cheaply. A progressive web app lets customers install the store to their home screen without an app-store listing, and a thin Android wrapper around the same site adds haptic feedback and keeps the payment round trip inside the app. Accessibility is not optional at any size: check colour contrast against WCAG AA, and automate that check so that a later design change cannot quietly break it.
A cash-on-delivery order touches a payment gateway, one or more couriers and an SMS provider before it is complete. Their sandboxes are rarely dependable enough to run an automated suite against, and no team wants a test texting real customers.
Build a small mock server that impersonates each external service, and make the base address of every integration configurable. Pointed at the mock, the full stack can run an order from checkout to delivery on a laptop: the mock gateway approves the payment, the mock courier accepts the booking and later reports delivery, and every message the system "sent" can be read back and asserted on. Guard the tests that send SMS so that they run only against the mock.
On top of that foundation, an end-to-end suite should cover the journeys that earn money — checkout with its validation and totals, phone sign-up and sign-in, the confirmation link, and the operations flow from confirmation through courier booking to delivery — alongside accessibility checks and a layout check at phone width.
Sit with the staff who process orders and record every step after checkout: who is called, what is re-typed, where orders stall. Fix the lifecycle states and the delivery zones before any interface is designed.
Stand up the commerce engine, database and storefront shell. Import the catalogue, implement phone-based identity and the single messaging function, and configure cash on delivery as the default payment path.
Build the confirmation link, the call queue, the risk score and the order screen that shows one next action. This phase delivers most of the operational value and should reach staff early.
Integrate courier booking, status mapping, webhooks and polling. Add the wallet and card gateways with server-to-server verification. Build the mock server alongside, not afterwards.
Rate limit the public endpoints, automate backups and restore tests, run the end-to-end suite against the mocks on every change, and load test the storefront against a production build before traffic is switched over.
Geekssort designs and builds headless commerce on Medusa and Next.js, custom software and multi-tenant SaaS platforms, with dedicated engineering teams based in Bangladesh serving retailers at home and clients across the US, UK and EU. The architecture in this guide is the one the team built for KeenCares, a Bangladeshi healthcare and wellness retailer that moved from a hosted storefront to a platform it owns — phone-based accounts, Bangla SMS, an order desk with risk scoring, Steadfast and Pathao booking, and bKash and SSLCommerz payments verified server to server. For a retailer whose store handles the checkout but not the business around it, a workflow assessment — where orders stall, what staff re-type, which fees recur — is the right starting point before any decision to build.
Refused and undeliverable parcels. Each one costs courier fees in both directions and ties up stock for the length of the round trip, which is why confirmation and risk scoring matter more than any storefront feature.
No. Guest checkout with a name, phone number and address should be the default. Accounts keyed to the phone number, with sign-in by one-time code, can be offered afterwards for order history and faster repeat purchases.
For some orders, yes. The link confirms the customers who are ready to commit and removes them from the call queue; staff then call only those who have not responded or whose phone number carries a poor delivery history.
Webhooks are occasionally delayed, duplicated or never sent. Polling open parcels on a schedule guarantees that every order eventually reaches its true status, and the webhook simply makes that happen sooner.
For a single-vendor store in one country, yes. Run the application, database, cache and reverse proxy as containers on one server with automated backups, and document the shortcuts that would need to change before scaling to several.
It depends on the size of the catalogue and the number of courier and payment integrations. The catalogue and storefront move quickly; the order desk, courier integrations and payment verification account for most of the engineering time and should be scheduled accordingly.


Ebrahim KhanFounder & CEO