Returns Management System Low-Level Design
A returns management system (RMS) handles the reverse logistics of e-commerce: accepting return requests, generating prepaid labels, tracking inbound packages, inspecting returned goods, and restocking or disposing of inventory. Interviewers probe this design to test your handle on workflow state machines, third-party carrier integration, and inventory side effects.
Requirements
Functional
- Accept return merchandise authorization (RMA) requests for eligible orders within a configurable window (e.g., 30 days from delivery).
- Generate a prepaid shipping label via carrier API and associate it with the RMA.
- Track inbound shipment scan events from the carrier.
- Record inspection results (condition, quantity discrepancy, fraud flag) when items arrive at the warehouse.
- Trigger inventory restocking or disposal based on inspection outcome.
- Initiate the refund or exchange workflow once inspection is complete.
Non-Functional
- RMA creation under 500 ms including label generation.
- Idempotent label generation — retries must not create duplicate shipments with the carrier.
- Audit trail for every state transition, retained for the life of the order plus seven years.
Data Model
- rma: id (UUID), order_id, user_id, status (REQUESTED | APPROVED | LABEL_ISSUED | IN_TRANSIT | RECEIVED | INSPECTED | COMPLETED | REJECTED), reason_code, notes, created_at, updated_at, version.
- rma_line: id, rma_id, order_line_id, sku_id, quantity_requested, quantity_received, condition (NEW | GOOD | DAMAGED | UNSELLABLE), disposition (RESTOCK | REFURBISH | DISPOSE).
- return_label: id, rma_id, carrier_id, tracking_number, label_url, idempotency_key (UNIQUE), issued_at, expires_at.
- inspection_record: id, rma_id, inspector_id, inspection_at, overall_condition, fraud_flag, notes.
- rma_event: id (sequence), rma_id, event_type, payload (JSONB), actor, occurred_at — append-only.
Core Algorithms and Flows
RMA Eligibility Check
On request the system validates: (1) the order exists and belongs to the requesting user, (2) the order status is DELIVERED, (3) the delivery date is within the return window, and (4) a non-rejected RMA does not already exist for the same order lines. Any failed check returns a structured error with a reason code the front-end can display without additional lookups.
Label Generation with Idempotency
The system generates a label idempotency key as SHA-256(rma_id + carrier_id + ship_from_zip) and stores it in return_label before calling the carrier API. If the carrier call fails the row is rolled back. On retry the key lookup finds no row and retries the carrier call cleanly. Once the carrier responds the label URL and tracking number are written atomically with the idempotency key, preventing duplicate label issuance on subsequent retries.
Inbound Tracking
The carrier delivers scan events via webhook. A webhook handler validates the HMAC signature, extracts the tracking number, looks up the return_label, and appends an rma_event row. When the DELIVERED scan arrives the RMA transitions to RECEIVED and a work item is published to the inspection queue.
Inspection and Restocking
A warehouse agent records per-line condition and disposition via the inspection API. Once all lines are inspected the system aggregates results: lines marked RESTOCK trigger an inventory increment for the SKU at the receiving warehouse; lines marked DISPOSE generate a disposal work order; lines marked REFURBISH route to a secondary processing queue. The RMA then transitions to INSPECTED and publishes a RETURN_INSPECTED event consumed by the refund service.
API Design
POST /v1/rmas— buyer creates a return request; returns rma_id and eligibility decision.GET /v1/rmas/{id}— returns RMA status, label URL, and line details.POST /v1/rmas/{id}/label— internal or buyer-triggered label generation (idempotent).POST /v1/rmas/{id}/inspection— warehouse records inspection results per line.GET /v1/rmas/{id}/events— full event history; cursor-paginated.POST /v1/webhooks/carrier-scans— carrier delivers tracking events; validates HMAC before processing.
Scalability Considerations
- Webhook ingestion is separated from processing: a lightweight receiver writes raw events to a queue (Kafka topic) and returns 200 immediately, preventing carrier timeouts from blocking event processing.
- Inspection queue workers scale independently of the API tier; peak load follows inbound package arrival patterns which are predictable from label scan events.
- Inventory restocking uses optimistic locking on the stock record to prevent race conditions when multiple RMAs for the same SKU are inspected concurrently.
- rma_event is partitioned by rma_id, keeping query performance stable as return volumes grow without requiring a global index scan.
- Label URLs are pre-signed S3 links with a 7-day TTL; the label PDF is stored once and never regenerated, eliminating redundant carrier API calls for re-downloads.
{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “How is RMA eligibility checked in a returns management system?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “RMA eligibility is evaluated against a rule set that includes return window (e.g., 30 days from delivery), item condition (non-perishable, not final sale), order status (delivered, not already returned), and merchant policy overrides. The service queries the order and fulfillment records, computes days since delivery, and runs the rules engine. Ineligible requests receive a structured rejection reason so the UI can display actionable messaging.”
}
},
{
“@type”: “Question”,
“name”: “How does carrier label generation work in a returns service?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “The returns service calls the carrier API (e.g., EasyPost, ShipEngine) with the return shipment details — origin (customer), destination (warehouse), package dimensions, and weight. The API responds with a label URL and tracking number. The service stores both, transitions the RMA to LABEL_ISSUED, and emails the label to the customer. Pre-signed S3 URLs are used if labels are stored in object storage so links expire and are not publicly guessable.”
}
},
{
“@type”: “Question”,
“name”: “What does an inspection workflow state machine look like for returns?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “States flow: RECEIVED → INSPECTING → (APPROVED | REJECTED | PARTIAL). On RECEIVED, a warehouse agent is assigned. In INSPECTING, the agent records item condition and quantity. Transitions to APPROVED trigger the refund service; REJECTED triggers a return-to-sender or disposal workflow; PARTIAL triggers a partial refund. Each state transition is persisted with a timestamp and agent ID, giving a full audit trail. Invalid transitions (e.g., APPROVED → INSPECTING) are blocked by the state machine.”
}
},
{
“@type”: “Question”,
“name”: “How is inventory restocking handled after a return is inspected?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “After an APPROVED inspection the returns service publishes a RestockEvent with SKU, quantity, and condition grade. The inventory service consumes this event and increments the available stock for sellable-grade items or routes unsellable items to a liquidation bin. Idempotency is enforced via the RMA ID so duplicate events do not double-count stock. Warehouse bin assignment is determined by condition grade using a configurable mapping table.”
}
}
]
}
See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering
See also: Shopify Interview Guide
See also: Stripe Interview Guide 2026: Process, Bug Bash Round, and Payment Systems