This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle
In India, a lot of online orders are cash on delivery. The buyer taps "COD", forgets about it, and when the courier shows up three days later they refuse the parcel, aren't home, or gave an address the courier can't find. The parcel goes back to the seller. That's an RTO, a return to origin.
The seller doesn't get the product's price back as profit. They lose the shipping out and the shipping back. RTO Shield uses ₹150 as the default cost of one RTO, and the shop can change it. For a small D2C brand making a few hundred COD orders a month, that adds up to a real slice of the margin, lost on orders that never had to ship.
Most of these returns can be predicted, and many can be fixed: confirm the order before it ships, correct a bad address, or nudge the buyer to pay up front. Sellers already know that. They just don't have the hours to message every risky buyer by hand.
So I built RTO Shield, an autopilot that does that work for Shopify sellers.
What I Built
RTO Shield connects to a Shopify store and works through every new COD order:
- Scores it. Every pending order gets a 0–100 risk score, a Low / Medium / High band and a one-line reason.
- Messages the buyer on WhatsApp. The message goes from RTO Shield's platform number with the shop's name on it, asking the buyer to confirm, or offering a prepaid link. The seller never pastes a token.
- Fixes the address. If the buyer replies with a corrected address, the API parses it and checks the pincode against India Post. If the city and state agree, the fix is written back to Shopify and confirmation carries on.
- Calls when nobody answers. A silent buyer moves on to a voice-agent call.
- Hands the leftovers to a human. Whatever still needs the seller lands on Exceptions, with Confirm, Cancel and one extra call. Nothing else needs their attention.
The seller mostly looks at two screens. Results shows the money saved. Exceptions shows the short list that needs a decision.
The demo shop's Results screen (demo data): ₹13,246 prevented, 3 COD orders converted to prepaid, 2 addresses fixed, 17 orders confirmed before they shipped.
Who pays, and why. The buyer is a small Indian D2C seller on Shopify who ships COD. The suggested price is ₹499 a month. At ₹150 an RTO, preventing four returns a month pays for it. Everything after that is margin they'd otherwise have handed to the courier. (This build doesn't take a card yet.)
There's also a robot voice tour that walks a new seller through the app in Hindi, English or Hinglish, because many of the people running these stores aren't developers and don't want to read docs.
Demo
- Live: www.rtoshield.tech
- Judges: press Try the demo on the landing page. There's no sign-up; it drops you into a demo shop with real screens and demo orders. The robot tour starts by asking for a language.
- Talk to the agent: open Exceptions and pick the order. In the demo you can start an in-browser call with the voice agent, so you can hear what a buyer would hear. You get two demo calls every 30 minutes, and Reset call starts a fresh one.
- The API and ML service run on Render's free plan and sleep when idle, so the first load can take about a minute. The site waits until both services report healthy.
Exceptions keeps a human in the loop: the order's history, Confirm / Cancel, and at most one extra call. In test mode the call is clearly labeled "Simulated call".
Code
Ranbir5ingh
/
rto-shield
Predict and prevent COD return-to-origin (RTO) losses for small Indian sellers with open-source AI (TabPFN + Gemma).
RTO Shield
Autopilot for cash-on-delivery returns. It scores an order, messages the buyer, fixes the address, and calls when nobody answers. The seller sees what stopped.
Live demo: www.rtoshield.tech
Suggested price: ₹499 a month. This build does not take a card.
The problem
A refused cash-on-delivery parcel in India still costs the return trip. RTO Shield uses ₹150 as the default cost of the shipping on one return-to-origin. The shop can change that number. Results counts rupees prevented as converted and cancelled risky orders, plus shipping. It is a planning figure, not a payout.
The buyer is asked before the parcel ships. A bad address is checked. A silent buyer gets a call. The orders that still need a person land on Exceptions.
Features
- Score. Each pending order gets 0–100, a Low, Medium, or High band, and a short reason. A pincode is courier context, not a ban.
- Autopilot WhatsApp.…
A monorepo with three services and an MIT license. CI runs lint, typecheck, unit tests, a production build and Playwright end-to-end tests (seller tour, judge demo tour, Exceptions) on every push, plus ruff and pytest for the ML service.
How I Built It
Shopify order ─▶ NestJS API (Render) ──▶ ML service (FastAPI, Render)
│ ├─ TabPFN: risk score from this shop's own history
│ ├─ scikit-learn: instant score on every new order
│ └─ Gemma 4: address-quality notes + message drafts
├─▶ WhatsApp Cloud API ─▶ buyer replies ─▶ address parse ─▶ India Post ─▶ Shopify write-back
├─▶ ElevenLabs voice agent (calls, Hindi/English/Hinglish tour)
└─▶ MongoDB Atlas (orders, users, audit; phones and addresses encrypted)
Next.js 15 web app (Vercel) ─ www.rtoshield.tech
The model learns the shop, not the pincode
The easy shortcut in RTO scoring is to blacklist pincodes. It's unfair to buyers, and it's wrong for a seller whose customers in that area pay fine. So in RTO Shield a pincode is not a model feature. The score uses 15 signals: order value, COD or prepaid, the hour (as a circle, so 11 PM sits next to midnight), the buyer's prior orders and prior returns, whether they're new, high-value COD, late-night orders, address-quality flags (incomplete, junk, landmark-only, city/state mismatch), and this shop's own return rate in that area, shrunk toward typical so one bad week can't swing it. The score never sees the buyer's phone or name.
TabPFN does the full scoring. It's Prior Labs' open-source tabular foundation model: it learns in context from the shop's own labelled history (delivered vs returned) and scores the pending orders in one pass. There's no training pipeline to run per seller, which matters when every shop has a different few hundred orders. On the live service it runs through Prior Labs' hosted tabpfn-client.
A new order needs a score straight away, before WhatsApp fires, so new orders, webhooks and the demo get an instant scikit-learn gradient-boosting score. TabPFN then rescores in full. If TabPFN is unreachable, the chain falls back to local TabPFN, then scikit-learn, then a rules prior, and scoring never just stops.
Gemma reads the address, code decides
Indian addresses are messy: "near Hanuman mandir, behind petrol pump", a missing house number, or a city that doesn't match the pin. Gemma 4 (gemma-4-26b-a4b-it) reads each address and returns structured quality notes, which become features and a plain-English reason for the seller. It also drafts the confirmation messages.
Gemma never gets the last word on facts. Whether an address is valid is decided by India Post's pincode data, not by a model: when a buyer sends a correction, it's only written to Shopify if India Post's city and state agree. If a check fails, that's recorded on the order and the fix is skipped.
WhatsApp without the setup
Asking a small seller to create a Meta Business account, register a number and paste tokens loses them on day one. So RTO Shield sends from its own platform number, with the shop's name on every template. A shop that wants buyers to see its own number can switch that on in Settings. Its token is stored encrypted and only that shop's messages move.
Calls, but autonomous
Calls run on an ElevenLabs conversational agent. The seller doesn't start calls; Autopilot does. From Exceptions the seller gets exactly one extra call per order, the result shows inline, and if the buyer answers, the order goes back into the pipeline. I capped it on purpose, because a tool that lets a seller ring a buyer five times turns into a harassment tool. ElevenLabs also voices the Hindi, English and Hinglish tour.
What's real today, and what's a switch
I'd rather say this up front:
- WhatsApp runs on Meta's test number. It works end to end. I've confirmed an order from my own phone. But a test number can only message numbers verified on the Meta app, and the store-name templates are waiting for Meta's approval.
-
Outbound phone calls are simulated. With
CALLS_MODE=browser, Autopilot and "Try one more call" record a clearly labeled Simulated call and dial nobody. The in-browser demo call is a real conversation with the agent. -
Going live is config, not code. Add a paid WhatsApp number, import a Twilio number into ElevenLabs, and set
CALLS_MODE=live. The steps are indocs/PRODUCTION.md. Sellers don't set anything up. - Demo numbers are demo data. ₹13,246 is the demo shop's figure. The model eval in the repo runs on a synthetic set, and I label it that way.
Built fast, with agents
I built this during the challenge week (first commit October 15) with AI coding agents doing much of the typing, while I made the product calls: no pincode bans, one extra call, platform-owned AI keys, Exceptions-first design. The whole stack runs on free tiers: Vercel, Render, MongoDB Atlas and free API tiers.
Why Does Open Innovation Matter?
The unit economics only work if the AI is cheap. A seller pays ₹499 a month, and a single RTO costs ₹150. If every order's score and every address check went through a per-token closed API, the AI bill would eat the margin I'm trying to save. TabPFN and Gemma are open, so I can run them wherever is cheapest. Today that's free hosted tiers. At scale it can be my own box.
Open models let me fit the problem instead of the API. Every Shopify store is a different, small dataset. TabPFN is built for exactly that: a few hundred rows, scored in context. It's open, so I can read how it works, run it locally (the code already supports the local tabpfn package) and fall back to scikit-learn without changing anything else. A closed "risk score API" would give me a number and ask me to trust it.
Seller and buyer data stays under our control. Buyer phones and addresses are some of the most sensitive data a small business holds. Gemma's weights are open, so the same address check can run on our own hardware through Ollama. Setting OLLAMA_BASE_URL switches it, and docker compose --profile ollama brings up a local Gemma. Today, honestly, production calls Gemma through Google AI Studio, because a free Render instance can't hold the model. The point is that I'm not locked in.
No lock-in on price or on a vendor. Every AI piece sits behind an env var with a fallback: TabPFN → scikit-learn → rules, Gemma → templates. If a provider changes its price or goes down, RTO Shield degrades instead of breaking.
Where I still use closed models: Gemini 2.0 Flash parses buyers' free-text address replies and powers the call agent's conversation, because it was the fastest reliable option for messy Hinglish on a free tier this week. It also sits behind templates as a fallback, and moving those jobs onto Gemma is the next step.
The stack is open too: Next.js, NestJS, FastAPI, scikit-learn and India Post's public pincode API. RTO Shield is mostly glue on top of other people's open work, and the repo is MIT so the next person can build on it.
My First User
I haven't charged anyone yet, and I won't pretend I have.
I messaged e-commerce business owners in my own circle, people who ship COD orders and deal with RTOs every week, and showed them what RTO Shield does. They told me they want to use it once it's live. What's holding it back isn't the product. It's the paid pieces: a verified WhatsApp Business number, a calling number and paid hosting. Right now everything runs on free and developer tiers because I don't have funding for those yet.
As soon as that's sorted, the switch is a config change, and I have sellers waiting to plug in their stores.
Prize Categories
-
Best Use of TabPFN: TabPFN does the full risk scoring. It learns in context from each shop's own delivered-vs-returned history across 15 features (no pincode) and scores the pending orders through Prior Labs'
tabpfn-client. It's the core of a wider prediction agent: the score decides who gets a WhatsApp, a prepaid offer, a call, or a spot on Exceptions. The live service reportsactive_predictor: tabpfn-client, and the fallback chain is local TabPFN → scikit-learn → rules. -
Best Use of Gemma: Gemma 4 (
gemma-4-26b-a4b-it, served through Google AI Studio) reads every address for quality problems: incomplete, landmark-only, junk, or a city/state mismatch. Those notes become model features and the seller-facing reason, and Gemma also drafts the confirmation messages. A local Gemma path through Ollama is built in. -
Best Use of Render: The NestJS API and the FastAPI ML service (TabPFN + Gemma) both run on Render from a
render.yamlBlueprint, with secrets kept out of the repo (sync: false), health checks, and the custom domains api.rtoshield.tech and ml.rtoshield.tech. - Best Use of ElevenLabs: A conversational ElevenLabs agent is the calling layer for buyers who don't reply. Judges can talk to it in the browser in the demo. ElevenLabs v3 also narrates the robot tour in Hindi, English and Hinglish. Outbound phone calls are simulated in test mode, and live dialing is one config switch.
- Best Use of MongoDB Atlas: Atlas is the data layer for orders, scores, WhatsApp threads, call outcomes, users and the audit log behind Results, Exceptions and Activity. Buyer phones and street addresses are encrypted at rest.
- Best Use of a .tech Domain: The whole product lives on rtoshield.tech: the app at www.rtoshield.tech, the API at api.rtoshield.tech and the ML service at ml.rtoshield.tech.
Built solo by Ranbir Singh.
If you sell on Shopify in India, how many of your COD orders come back? I'd love to hear what you'd want RTO Shield to handle next.



Top comments (0)