Sendy
Multi-tenant delivery & e-commerce operations platform
- ASP.NET Core .NET 8
- PostgreSQL
- EF Core
- SignalR
- Hangfire
- React 19
- TypeScript
- Claude & OpenAI
- MCP
- Cloudflare
- Clean layered architecture at scale — 163 controllers, 90 domain entities, 55 migrations, backed by 676 xUnit tests.
- Every store capability ships on two surfaces: JWT for the dashboards and a parallel API-key surface for external systems, guarded by 231 permissions across 679 endpoint checks with per-key rate limiting.
- Pluggable provider framework integrating three delivery companies (Al-Waseet, Boxy, Hi-Express) and five payment gateways (QiCard, AsiaPay, ZainCash, Wayl, SwiftPayIQ) with HMAC-verified webhooks and automatic order sync via EF Core interceptors.
- Real-time order tracking and customer chat over three SignalR hubs, tokenized public tracking links, and storefronts on slug subdomains and Cloudflare-verified custom domains.
- Omnichannel messaging — WhatsApp, Instagram, Messenger and website chat funnel into one conversation model answered by the store's chosen AI agent (Anthropic or OpenAI), metered per store for billing.
- An OAuth-secured MCP server with dynamic client registration and scoped tokens, exposing store operations to AI agents.
- Full retail operations beyond delivery: inventory, purchase orders, suppliers and warehouses, a cashier POS with thermal label and receipt printing, KYC verification, support tickets, ratings and expense tracking.
- Fintech workflows end-to-end — wallets, commissions, cash handover, payouts, invoices, and subscription billing.
- Five React 19 + TypeScript frontends — admin dashboard, driver app, landing and storefront, customer mini-app, and a Next.js developer-docs site — all Arabic/English.
Architecture & decisions
Constraint
Three delivery companies and five payment gateways, each with its own contract, credential model and status vocabulary — and six kinds of caller (admin, store, branch, delivery company, e-commerce platform, driver) who all see the same order differently. On top of that, every store capability has to be reachable twice: once by our own dashboards and once by a merchant's existing ERP or storefront.
Approach
One provider interface with per-store credentials, so adding a fourth
courier is an adapter rather than a branch in shared code. Authorization
is permission-based at the endpoint rather than role-based at the
controller — 231 named permissions checked at 679 call sites — so a new
caller type does not require re-auditing every action. The integration
surface is a genuine second set of controllers over the same services
rather than the same endpoints with a looser guard, so an API key can
never accidentally inherit a dashboard's reach. Audit logging hangs off
an EF Core SaveChanges interceptor instead of each call
site — a write cannot be added that forgets to record itself.
Trade-off
Two surfaces over one service layer means roughly a third more controllers to keep in step, and a capability is not shipped until both are done. That is a real and recurring cost. It buys the ability to change the dashboards without breaking a merchant's integration, which on a platform other businesses build against is worth more than the controllers it costs. Interceptor-driven auditing makes the same kind of trade: the write path is no longer visible at the point of the call, but coverage is guaranteed rather than remembered.
Outcome
163 controllers over 90 entities and 55 migrations, backed by 676 xUnit tests, running in production with eight provider integrations, custom domain storefronts, real-time tracking, omnichannel messaging and five React frontends.