Corporate food ordering sounds straightforward until you map out what it actually involves. A company places a daily standing order for 80 employees across three office locations. Each location has a different set of approved restaurants. Employees have individual dietary preferences, meal budgets, and copay settings. Restaurants need pickup schedules, order counts, and payment reconciliation. Drivers need routing. Finance needs invoices that sync to QuickBooks. HR needs a nutrition report at the end of the month.

That is the operational surface Chowmill (chowmill.com) was built to manage. Not a single ordering flow -- a multi-sided operations platform connecting companies, restaurants, delivery drivers, and administrators into one coordinated system.

We built the platform from the ground up: three separate web applications, a central Rails API, and the integration layer that ties every moving part together.

The Core Problem: Three Stakeholders, Three Completely Different Workflows

Most food delivery products are consumer-facing. You open an app, you pick food, you pay, food arrives. The complexity is in logistics. B2B corporate food ordering adds two more stakeholder layers on top of that -- and each one has workflow requirements that would break a consumer product if you tried to serve them from the same interface.

The three stakeholders in the Chowmill model are:

  • Company employees and admins: Employees browse menus, place individual orders within their company-defined budget. Admins set up recurring meal schedules, manage delivery locations, control per-meal budgets and copay rules, and review reports.
  • Restaurant vendors: Restaurants receive incoming orders, acknowledge them before cutoff, manage their menu listings, view invoices, and track their earnings through a separate billing dashboard.
  • Platform admins: Internal staff who configure the full system -- companies, restaurants, drivers, pricing, delivery logistics, and all the operational levers that sit between the two sides of the marketplace.

Serving three stakeholders with fundamentally different jobs-to-be-done from one application creates UX and security complexity that grows exponentially. The architecture decision we made early -- three separate React frontends sharing a single Rails API with role-scoped endpoints -- was the right one. Each portal was built, tested, and deployed independently, with no risk that a vendor-facing code change could touch a company admin's data.

The RunningMenu Engine: Automating Recurring Corporate Meal Scheduling

The central domain model in Chowmill is the RunningMenu -- an individual meal event with a delivery time, a cutoff time, a set of participating restaurants, and a collection of employee orders attached to it. One RunningMenu maps to one lunch, one dinner, or one breakfast, at one or more delivery locations.

But companies do not want to create a RunningMenu manually every day. They want to say "every Monday, Wednesday, and Friday, order lunch from these three restaurants, deliver to our downtown office at 12:30pm, with a $15 per-person budget." That is the RecurringScheduler -- and it is where most of the operational intelligence in the platform lives.

The RecurringScheduler model tracks day-of-week recurrence flags (monday, tuesday, wednesday through sunday), start dates, cutoff windows, admin cutoff windows, per-meal budgets, copay rules, cuisine preferences, and same-day delivery configurations -- all version-tracked via PaperTrail so the full audit history is preserved for billing disputes or compliance review.

When a scheduler fires, it generates a RunningMenu with pre-populated restaurant assignments, delivery times, and cutoff deadlines. Employees see the menu in their portal automatically. No admin action required.

This single engine is what makes Chowmill operationally viable for enterprise customers. A company managing meals for 200 employees across five offices cannot have someone manually creating meal events every day. The RecurringScheduler removes that entirely.

Delivery Logistics: Onfleet and Senpex Integration

Once orders are locked in at cutoff, meals need to move from restaurants to offices. Chowmill uses two delivery service providers -- Onfleet and Senpex -- selectable per scheduler, giving operations teams flexibility based on geography and provider availability.

The Onfleet integration is the more complex of the two. For each RunningMenu, the OnfleetTaskService groups delivery addresses by assigned driver and creates a chained task graph: one pickup task per restaurant (with the exact meal count, pickup window, and restaurant address), followed by a dropoff task at the company office (with delivery instructions, recipient phone number, and a photo requirement on completion).

Task dependencies are explicit in Onfleet -- the dropoff task cannot be marked complete until the pickup tasks that feed it are done. This gives the operations team real-time visibility into delivery status without anyone manually tracking driver progress. If a pickup is running late, the system knows before the company admin does.

Error handling in the delivery layer is also non-negotiable. If an Onfleet task creation fails -- because a restaurant address changed, a driver is unavailable, or an API timeout occurs -- the system logs the failure, sends an alert email to the operations team, and records a structured error entry so the failure can be investigated and retried without losing the original context.

Multi-Channel Notifications: FCM, Twilio, and Email in One Pipeline

A corporate food platform lives or dies on timely communication. Employees need to know when a menu is available, when their cutoff is approaching, and when their food has arrived. Restaurants need to know how many meals to prepare and when the driver is picking up. Admins need alerts when things go wrong.

We built a notification pipeline with three channels working in parallel:

  • FCM push notifications: The PushNotificationService handles per-user device token management and delivers contextual messages at key events -- "You're missing out" alerts 24 hours before cutoff, "Last chance to order" alerts one hour before cutoff, and "Your food is here" confirmations when delivery is marked complete. Token management is multi-device: one user can have multiple registered devices and all receive the notification.
  • Twilio SMS: The TwilioSmsService handles restaurant-facing communication. Restaurants receive order confirmations with meal counts, pickup times, and a direct link to their order details page. SMS logs are stored per-contact so the full message history is auditable.
  • Email: Transactional email via ActionMailer covers order confirmations, cutoff reminders, invoices, delivery receipts, and admin alerts. All outbound emails are logged to an EmailLog model with sender, recipient, subject, and a base64-encoded body -- so the exact content of any system email can be retrieved after the fact.

Notifications are not fire-and-forget. Sidekiq background workers handle the actual dispatch, with Redis-backed queuing that survives server restarts. Each notification type runs in its own named queue so a spike in email volume cannot block push notification delivery.

Billing Architecture: Stripe, Invoices, and QuickBooks Sync

The billing model in Chowmill has more moving parts than most SaaS products because it serves two distinct billing relationships simultaneously: companies paying for employee meals, and restaurants receiving payouts for fulfilled orders.

On the company side, each company has a Billing record that tracks whether they operate on invoice terms or credit card. Credit card companies are charged via Stripe using stored payment methods. Invoice companies receive consolidated invoices at the end of each billing period, with full line-item breakdowns across all RunningMenus, delivery fees, and adjustments.

On the restaurant side, RestaurantBilling tracks per-order payouts with a separate job queue for payout calculation. The restaurant_payout_order_calculation_job runs after each RunningMenu closes, aggregating all fulfilled orders and generating the payout records that feed into the finance dashboard.

Both sides of the billing stack sync to QuickBooks via the quickbooks-ruby gem. The UploadToQuickbookJob processes invoices in batches, refreshing OAuth2 tokens as needed, and updating invoice records in the database when the sync completes. The entire accounting trail -- from employee order to restaurant payout to QuickBooks entry -- is traceable through the platform without any manual reconciliation.

Elasticsearch-Powered Menu Discovery

Finding the right food at the right time is the experience that company employees interact with most. The menu search layer uses Elasticsearch via the Searchkick gem, with the search index covering restaurant addresses, food items, and cuisine types.

Reindexing is event-driven, not scheduled. When a restaurant updates a food item, a FooditemReindexJob runs asynchronously and updates only the affected document. When a restaurant address changes status, the RestaurantAddressReindexJob updates all associated documents in a single background pass. Search results stay consistent without any user-facing downtime or manual rebuild steps.

This matters in a marketplace context because restaurant menus change frequently. New items get added, pricing changes, items go out of season. If the search index lags behind the source of truth by even a few hours, employees are browsing stale menus and placing orders against food that may no longer be available. The event-driven reindex approach keeps the catalog current in near-real-time.

Reporting: Nutrition, Satisfaction, Vendor Analytics, and Budget Tracking

Enterprise clients do not just want meals delivered. They want data about those meals. HR wants to know whether employees are ordering healthy options. Finance wants to know whether per-person spend is tracking within budget. Procurement wants to know which restaurants are performing best on delivery accuracy and satisfaction scores.

The reporting layer is built on a set of materialized database views that aggregate raw order data into pre-computed report tables. RepNutrition tracks dietary breakdowns per employee per delivery location. RepSatisfaction aggregates order ratings by restaurant and time period. RepBudgetAnalysis tracks actual spend against per-meal budgets by company and address. RepVendor provides restaurant-level performance metrics visible in the vendor portal.

Reports are incrementally populated -- each model checks the last populated date and only imports new records, using activerecord-import for bulk inserts that do not generate N+1 queries on large datasets. The result is a reporting system that stays fast even as the transaction volume grows.

The Vendor Portal: A Self-Serve Operations Hub for Restaurants

The vendor-facing React application is an independent portal that restaurants use to manage their participation in the platform without any involvement from Chowmill's internal team.

The core vendor workflows are:

  • Order acknowledgment: Restaurants see incoming orders grouped by RunningMenu and must acknowledge them before cutoff. The AcknowledgeOrders page shows meal counts, individual order details, and the pickup window so kitchen staff can plan prep time accurately.
  • Menu management: Restaurants upload and manage their own menu items, pricing, dietary flags, and photos. Menu uploads are processed asynchronously via a background job, with image uploads going to AWS S3 via CarrierWave.
  • Invoices and billing: Restaurants view their payment history, download invoice PDFs, and track outstanding amounts due -- all without needing to contact Chowmill's finance team.
  • Reports: Vendors see their own performance data -- order volumes, satisfaction scores, and earnings trends -- through a filtered view of the same reporting infrastructure used by company admins.

Giving restaurants self-serve access to their data is not just a UX improvement. It is an operational multiplier. Every invoice question a restaurant answers themselves is a support ticket that never gets created.

The Client Portal: Employee Ordering and Company Administration

The company-facing React application serves two personas with very different needs from the same authentication boundary: employees who order food and admins who configure everything.

Employee-facing flows are deliberately simple. The Menu page shows available dishes for an upcoming RunningMenu, filtered by cuisine preferences and dietary requirements set in the user profile. The Cart handles order submission with real-time validation against the company's per-meal budget and cutoff deadlines. OrderHistory gives employees a view of their past orders with receipt-level detail.

Company admin flows are significantly more complex. Admins configure recurring schedulers, manage approved restaurant lists per delivery location, set individual employee budgets and copay rules, invite new users, and review the full reporting suite. The Reports section surfaces nutrition, satisfaction, and spend data in chart and table formats, with export capabilities for finance teams.

Both portals are built in React with TypeScript, Redux for state management, and full i18n support via react-i18next -- Chowmill serves clients in multiple markets and internationalization was a first-class requirement, not an afterthought.

The Admin Panel: ActiveAdmin Powering Internal Operations

The internal operations team needed a powerful, configurable admin interface without the cost of building a custom one from scratch. We used ActiveAdmin with the Arctic Admin theme to provide a full-featured dashboard covering every model in the platform.

Key internal capabilities include: full company and restaurant management, RunningMenu monitoring with status overrides, driver assignment, delivery task management, billing configuration, QuickBooks sync controls, and Elasticsearch index management. Permissions are enforced via CanCanCan, with role-based access control that restricts internal team members to only the data they need for their specific function.

The admin panel also surfaces the Slack integration. The SlackWebService and slack-ruby-client gem power internal alerting for delivery failures, order anomalies, and billing exceptions -- the operational signals that need immediate human attention rather than a queued notification.

The Stack

  • Backend: Ruby on Rails 7.0, PostgreSQL, Sidekiq + Redis
  • Search: Elasticsearch 7.17 via Searchkick
  • Client portal: React, TypeScript, Redux, react-i18next, Webpack
  • Vendor portal: React, TypeScript, Redux, react-i18next, Webpack
  • Admin panel: ActiveAdmin with Arctic Admin theme, CanCanCan authorization
  • Delivery: Onfleet (primary), Senpex (secondary), driver-assignable per scheduler
  • Payments: Stripe for card billing and subscriptions
  • Accounting: QuickBooks via quickbooks-ruby with OAuth2 refresh
  • Notifications: FCM (push), Twilio (SMS), ActionMailer (email)
  • File storage: AWS S3 via CarrierWave and fog-aws
  • Audit trail: PaperTrail on all critical domain models
  • CRM: HubSpot integration via hubspot-api-client
  • Infrastructure: Docker, Docker Compose, Bitbucket Pipelines CI/CD, Capistrano deployment

What Shipped

The Chowmill platform at production includes:

  • Three independent, production-deployed web applications sharing a single API
  • A RecurringScheduler engine that auto-generates daily meal events with zero manual input
  • Full Onfleet and Senpex delivery orchestration with chained pickup-dropoff task graphs
  • Multi-channel notification pipeline across FCM, Twilio SMS, and email with full message logging
  • Stripe billing and QuickBooks accounting sync covering both company invoicing and restaurant payouts
  • Elasticsearch-powered menu search with event-driven near-real-time index updates
  • An incremental reporting stack covering nutrition, satisfaction, vendor performance, and budget analytics
  • Full i18n support across both end-user portals
  • Role-scoped admin panel with Slack alerting for operational exceptions
  • 630+ database migrations reflecting the depth and maturity of the domain model

What This Project Demonstrates

Chowmill is not a complex project because the technology is exotic. Rails, React, Stripe, and Elasticsearch are all well-understood. It is complex because the domain has real operational depth -- recurring scheduling, multi-sided billing, delivery logistics, and reporting requirements that all need to work together correctly, every day, without manual intervention.

The architecture decisions that made this work -- separate portals per stakeholder, event-driven reindexing, chained delivery task graphs, incremental report population -- are not visible to end users. They only become visible when a platform that handles this volume of daily transactions runs without operational escalations.

That is what production-grade SaaS means. Not a demo that works once. A system that works the same way on day 500 as it did on day 1.