Personal project · staging
A return-and-refund tracker that reads commerce emails, computes return deadlines, and vaults wallet passes — built as a polyglot monorepo to keep the slow work off the request path.
NestJS 10 · Prisma · PostgreSQL 16 · Redis · BullMQ · Expo / React Native · Go 1.22 · Terraform (AWS VPC · RDS · ElastiCache · S3) · Swagger/OpenAPI
What it is
ReturnRider ingests commerce signals from a user's inbox, works out when each purchase's return window closes, tracks the shipment, watches for the bank refund, and stores the QR pass in Apple or Google Wallet. It is a personal project I built to practice a real multi-service system end to end — the interesting part is the shape, not a launch.
The problem
Reading and parsing inboxes is slow, bursty, and I/O-heavy — exactly the kind of work you never want on an API's request path. It also touches the most sensitive data a user has, so email access needs a hard boundary, and any single ingestion path (an OAuth grant, an uploaded receipt) will sometimes fail. The design question is how to absorb all of that without blocking the user or the API.
What I built
Architecture
Expo / React Native app
NestJS API
Prisma · DTO validation · OpenAPI
PostgreSQL
orders · returns · refunds
Redis · BullMQ
ingestion + parse jobs
Go email-sync worker
Gmail pull + parse, off the API
External
Gmail · Plaid · EasyPost · Wallet
Two decisions and their tradeoffs
Inbox pulling and parsing is long-running and CPU/I-O heavy, so it lives in its own Go service that consumes jobs from Redis rather than running inside the request-handling API. The cost is a polyglot codebase — two runtimes, two deploys. The gain is that parsing can crash, retry, and scale on its own without ever touching the API's latency or event loop.
Auto-discovery from a connected inbox is the best experience, but gmail.readonly is a broad, sensitive grant that a user can decline or that can fail in production. So manual add and receipt scan are first-class backup paths, and the product rule is that email data is never sold. The tradeoff is more entry code paths to build and keep correct — worth it so a missing OAuth grant never leaves a user stuck.
Status