All projects

Case study · Cabify Spain · Apr to Sep 2026

Email-to-Reservation Pipeline (B2B)

A corporate client with a dedicated fleet sends its service requests as spreadsheets by email, and the fleet partner sends back signed work orders as PDFs. A support team used to type every trip into the platform and pre-assign the driver by hand, every day. Now two agents read both documents, a sheet compares them line by line, and the ride goes out through the public API with the driver already attached.

Email in, ride outn8n automation
n8nn8nClaudeClaudeGmailGmailGoogle DriveGoogle DriveGoogle SheetsGoogle SheetsGoogle Apps ScriptGoogle Apps ScriptSlackSlack
My roleDesigned and built the workflows, prompts, sheet logic and API integration.
WhenApr to Sep 2026 · multi-client engine from 15 Sep
Built withn8n, Claude through the company LLM gateway, Gmail, Drive, Google Sheets and Apps Script, the public ride API, Slack
StatusIn production for one client; the multi-client version is being rolled out.
2
documents from two senders, reconciled line by line
1 call
to create the ride and pre-assign its driver
35 min
minimum lead time, checked again on the server
63×
cheaper per agent run after the cost fix

How it works

INBOUNDREADCOMPAREAPPROVEACTPLATFORMwrites backRequest emailsclient Excel files,mailbox to DriveWork orderspartner PDFs, mailbox toDriveTwo Claude agentsextract fields, writethrough narrow toolsOperations sheetone row per service-day,live CHECK columnOperatorreads the status, clickscreate or cancelAction webhookre-validates, geocodes,estimates, picks theproductPublic ride APIride + preferred driverin one call
The agents only read and compare. A ride is created by a deterministic webhook, and only after a person clicks its link.
  1. Intake. Two mailbox watchers save the client's request files and the partner's work orders to Drive.
  2. Reading. A requests agent and a work-order agent extract every field. Their tools are deliberately narrow: find a row, upsert a request, update a work order, create a row when a work order has no request, and notify Slack. They can only write value columns; formula columns and anything that identifies a ride are out of their reach.
  3. Cross-check. The sheet is the operator's interface: one row per service-day and a CHECK column that compares request and work order live (OK, warning, mismatch, no request), with the differing cells highlighted. The formulas use VSTACK arrays, because curly-brace array literals break in a Spanish locale.
  4. Action. Each row has create and cancel links. They call a webhook that checks everything again on the server: the row must be OK, it must not already have a ride, and there must be at least 35 minutes of lead time. Then it geocodes both addresses, gets a price estimate, picks the product by duration and creates the ride. The ride ID and price are written back to the row.
  5. Edge cases. Request versions, services spanning up to 31 days, overnight trips, cancellations and work orders with no matching request all have explicit handling. Anything unclear posts a "human review required" message.
Operations sheet · client A · invented data
DayTimeRouteCHECKActionJourney
Mon 1407:30Office A → Studio 2OKCreateCancelJ-8F21
Mon 1409:15Studio 2 → Airport T4OKCreateCancel
Mon 1413:00Studio 1 → HotelMISMATCHblocked
Tue 1506:45Home → Studio 2WARNINGCreateCancel
Tue 1518:30Studio 2 → StationNO REQUESTblocked
The operator's view, with invented data. A mismatch or a missing request blocks the create link until someone fixes the row.
n8n · request and work-order agents · 23 nodes
n8n diagram of the two reading agents with their tools
The two reading agents. Dashed lines are the model and the tools each agent is allowed to use.
n8n · create / cancel ride · 29 nodes
n8n diagram of the create or cancel webhook
The action webhook: validate, authenticate, geocode, estimate, pick the product, create or cancel, verify, write back and notify.

Driver pre-assignment

Every trip for this client needs a named driver. For a while that meant a manual step, because the internal service that reserves drivers was not reachable from n8n. I documented the volume that depended on it and opened a request with the product team. In September we moved to the public API's preferred-driver field, so creating the ride and assigning the driver is one call, behind a feature flag for the accounts that need it. Next: an automatic check after each call that the driver really is attached.

One engine for many clients

The same design is now generalised: one webhook per client, per-client configuration and product rules in n8n data tables, product selection that prefers an exact match over a wildcard and then priority, and a single error collector that writes to the sheet, posts to Slack and returns a clear 422. Payloads are built with JSON.stringify, so quotes and accents in addresses never break a request.

n8n · multi-client engine · 38 nodes
n8n diagram of the multi-client ride engine
The multi-client engine: several client webhooks share one path.

What I learned

  • The agents read and compare; a person approves and a deterministic webhook acts. That split is what made the operations team comfortable using it.
  • The first version was expensive for a reason that had nothing to do with the model: one tool returned a whole sheet on every call. The fix is in LLM cost control.

The client, the fleet partner and all volumes are left out on purpose. The sheet above is an illustration with invented rows.