Case study · Cabify Spain · Sep 2026
RPA Uploader · Mac mini
The internal RPA tool runs bulk changes from a CSV: bonuses, licences, vehicle products, attachments. Uploading meant opening the right panel, attaching the file, waiting and checking the result by hand. Now you drop the file in a Drive folder. A service on the team's Mac mini works out which RPA it belongs to, uploads it, waits for the real outcome and posts it to Slack with every link.

How the team uses it
Three steps, and two of them are waiting. Drop a real .csv in the folder; wait about a minute, since the Mac mini checks every 60 seconds; read Slack, where two messages arrive: one when the file is accepted and one with the real result.
File:
bonus_adhoc_august.csv · status: running. I'll post the real result when it finishes.8 of 3,584 rows failed: motor_type can't be blank (×7) · asset not found (×1)
The CSV lists 3 documents;
docs.zip is missing 2. Nothing has been sent.Recognising the product by its columns
Each RPA product expects specific columns, its signature. The watcher normalises the header of the CSV, compares it with the 33 signatures and keeps the best match above 0.6. Real files are called things like "entries (3).csv", so the file name is useless; a file it cannot place is not sent. The first time a new type goes through, someone checks that first upload before trusting the automation.
# Which RPA does this file belong to? Decide by content, never by name. def detect_product(header, signatures, min_score=0.6): cols = {normalise(c) for c in header} best, score = None, 0.0 for product, expected in signatures.items(): s = len(cols & expected) / len(cols | expected) # Jaccard similarity if s > score: best, score = product, s return (best, score) if score > min_score else (None, score) def plan_parts(size_kb): if size_kb <= 20: return 1 # about 5 minutes: not worth splitting if size_kb <= 495: return 5 # five equal parts, all at once return math.ceil(size_kb / 99) # 99 KB parts, in batches of five
Splitting: why and how
The RPA accepts much larger files. The problem is time, not size: it processes about one row per second, with remarkable regularity. I measured it over a week of real runs rather than guessing.
That gives the rule. Up to 20 KB a file goes whole, because it takes about five minutes anyway. From 20 to 495 KB it is split into five equal parts that run at the same time. Above that, 99 KB parts go in batches of five, and the next batch starts when the previous five finish. There is one Slack message at the end, not one per part.
Products that need two files
Attachment uploads need a CSV and a ZIP with the documents. The watcher pairs them by content: the CSV's file column names each document, and the ZIP that contains all of them wins. Pairing by name would have failed on day one, and pairing by upload time would cross the files of two people working at once.
| Situation | What the system does |
|---|---|
| Only the CSV has arrived | Waits and posts after 2 minutes. Upload order does not matter. |
| Only the ZIP has arrived | Waits quietly; the daily update lists it as a ZIP with no CSV. |
| The ZIP is incomplete | Names exactly which documents are missing, and sends nothing. |
| The pair is complete | Sends both at once, each to its own field. |
When the session drops at night
The RPA has no API; it is reached through a browser session that a person renews every morning. If a file arrives at 03:12, the watcher posts one message saying there is no session, not one a minute. Between 20:00 and 08:00 it opens no windows and stays quiet. At 09:05 someone logs in and the queued file goes out on its own. Nothing is lost.
How you know it is still alive
An automation that fails silently is worse than none. Each of these alerts exists because the failure it covers already happened once:
| Alert | The failure it covers |
|---|---|
| The watched folder does not exist | On 4 September the folder was renamed and the service stood still for three days without anyone noticing. |
| Drive is not running | The folder is still there but frozen: nothing arrives and it looks like there is no work. |
| I cannot read the file | Drive had not downloaded it yet. It retries and posts on the third attempt. |
| File not processable | An .xlsx or a native Sheet that would never have been sent, silently. |
| Daily update at 09:00 | Any failure nobody predicted: if the message does not arrive one day, something is wrong. |
A macOS detail took the longest to find: background processes start with on-demand downloads switched off, so files Drive had not downloaded were unreadable to the service. The fix was a small launcher with the right permissions and a policy the watcher re-enables at start. The old workaround, marking the folder as available offline, is no longer needed. The log also limits itself: above 5 MB it is archived and a new one starts.
What I learned
- Measure the slow system before designing around it. The one-second-per-row pace made the splitting rule obvious.
- With payment files, never touch the content. Use the original bytes or do not send it.
Internal tool names, hosts, people and file contents are left out on purpose. The Slack messages above use invented file names.