MaddyCustom / Work story / 11 min read
From a custom design to a delivered order.
I co-founded MaddyCustom and built its original commerce platform from scratch in code. I wrote the storefront, backend business logic, admin tools, and AI assistant. This was a custom Next.js and MongoDB product, not a Shopify store.
The starting point
When someone buys a custom vehicle wrap, the website is only the beginning of the order. A design needs to match the right vehicle or product variant. Payment needs to be understood. Someone needs the correct artwork for production. Stock has to be accounted for. The parcel has to reach the customer, and support needs to know what happened if it does not.
I co-founded MaddyCustom and built its original platform from scratch. Harshit handled the business side; I wrote the application and business logic across the storefront, backend, and separate admin platform. This was custom software built with Next.js and MongoDB, not a Shopify theme or a collection of store plugins. The AI shopping assistant was part of that engineering work too.
MaddyCustom reported 100K+ monthly users and approximately $62.4K USD in annual revenue. Supporting that business meant looking beyond conversion at checkout. The software also had to help a small team understand payments, prepare the right products, and follow an order through delivery.
I wrote the application logic across the customer journey and the team’s operations: catalogue and variants, search, cart, offers, checkout, payment verification, orders, production files, inventory, shipping, analytics, and the shopping assistant. The storefront and separate admin app were built together around the way this business worked.
01 / Next.js · React · Redux · MongoDB · Atlas Search
A storefront built around custom products.
Buyers can find the right design and variant, understand their options, and carry those choices into checkout.
I built the Next.js storefront around a category, specific-category, and variant hierarchy. Product search, recommendations, persisted cart state, custom offers, and partial-payment options support the needs of vehicle wraps and accessories. The backend models carry those choices through the order instead of losing them at checkout.
The custom code covered the whole journey: catalogue structure, product variants, discovery, cart persistence, offer rules, checkout, payment handling, and order state. On the operational side, I built the rules and screens for production files, inventory, shipping, access control, and analytics. Payment and shipping providers supplied their external services; I wrote the logic that connected them to our orders and the team’s workflow.
The catalogue needed more structure than a collection of product names and prices. A design could belong to a category, a more specific category, and a particular variant. Those relationships affected where it appeared, what a buyer could select, and which production file the team would need. I carried that structure into the database and the browsing experience instead of trying to repair it later in the order flow.
The customer-facing app used Next.js, React, and Redux. Persisted state mattered because shopping is rarely one uninterrupted session. A buyer can inspect a design, change a variant, open the cart, leave, and return. Cart state, offer eligibility, and the selected product details need to agree when they do. A polished product page does not help much if the checkout forgets what the customer chose.
Search had to work with how people describe products. The implementation combines text search, fuzzy matching, field weighting, and availability checks. I also built product recommendations and category-specific presentation. The goal was to help a buyer reach a suitable product without requiring them to understand the structure of our catalogue.
Offers introduced another layer of rules. A discount can depend on cart value, quantity, earlier orders, or a particular bundle. Putting those conditions into a model made the behavior explicit and gave the admin side a way to manage it. It also kept pricing decisions closer to the backend that creates the order, where they can be checked again.
02 / Razorpay · PayU · webhooks · payment verification · MongoDB
Payments with a recovery plan.
A gateway problem should have an alternative path, and a repeated callback should not mean a repeated order.
I built Razorpay and PayU payment paths and health-aware provider selection. The chosen provider and decision metadata stay with the order for debugging. Server-side verification and guarded post-payment handling connect payment outcomes to stock, fulfillment, and conversion tracking. Split shipments and partial payments require totals to stay correct across the whole order.
A payment integration is not finished when the success callback opens a confirmation screen. The backend has to establish what was paid, for which order, through which provider, and what should happen next. It also has to cope with a callback arriving again or reaching the server after the customer has closed the page.
I implemented paths for Razorpay and PayU, including provider selection based on health signals. The selection code records the preferred provider, the recommendation, and the reason for that decision. That record is useful later: an operations issue is much easier to investigate when the order explains how it entered a particular payment path.
Provider status is still only a signal. It cannot promise that a particular bank or payment attempt will work. I treated fallback as a recovery option rather than a guarantee. The important part was making the decision explicit and connecting successful verification to a guarded post-payment flow.
Partial payments and split shipments made the accounting more interesting. An order can carry a paid portion and a remaining amount, while its fulfillment may be divided into linked orders. The admin view needs to aggregate those amounts correctly. I worked on those totals as well as the payment path, because an incorrect operations view can be just as costly as an incorrect checkout.
The post-payment flow touches inventory, coupon usage, fulfillment, and notifications. Database transactions help with changes inside MongoDB; an external shipping or messaging request is a separate side effect. I handled those boundaries separately so that a repeat or a partial failure had a sensible outcome.
03 / Next.js · Clerk · S3 · Shiprocket · role-based access
Software for the team behind every order.
Production can get the right artwork, operations can track fulfillment, and support can see the context behind an order.
I built a separate admin app for order operations, inventory, design search, production-template downloads, packaging, review moderation, offers, and department access. Shiprocket integration maps delivery events back into order state. Tax exports and shipment-aware payment totals connect the operational view to finance.
The admin application became the other half of the product. Customers saw designs and a checkout. The team saw orders, production downloads, stock, packaging, support questions, and shipment states. Those are different views of the same work, and each needed enough context to act without repeatedly asking another person what to do.
Production downloads are a good example. A generic export of the order table would not solve the problem. The team needed the correct templates for the selected design and variant. I built design search, template handling, and download workflows that connected those files to what had actually been ordered. Later fixes around multiple templates, variant availability, and exact search were part of keeping that workflow usable.
Inventory has a similar connection to real work. Available and reserved quantities are not the same thing. A cancellation and a delivered order should not trigger the same stock behavior. The shipping integration maps external delivery events into the order lifecycle so the internal view can stay useful as the parcel moves.
I also built department and role access, review moderation, offer controls, packaging tools, and tax exports. These are not the most dramatic screens in a portfolio, but they reduce the number of manual checks a small team needs to make. Building them taught me to ask who will use the result on an ordinary busy day, not just whether the feature looks finished in a demo.
04 / Event ingestion · MongoDB aggregation · Recharts · Meta CAPI
Know where customers get stuck.
The team can follow the journey from a visit to payment, then investigate drop-offs and repeat purchases.
I built first-party funnel events and sessions, event deduplication, UTM attribution, customer journey views, and admin comparisons. The dashboard includes funnel timing, drop-off analysis, and first-purchase categories that lead to repeat buyers. I also implemented Meta server-side conversion tracking, which was later disabled in the archived storefront.
A sales total tells you what happened. It does not explain where a customer stopped. I built a first-party event model that follows the steps between a visit, a product view, the cart, the order form, the payment stage, and purchase. Events and sessions carry the context needed to connect those steps.
Deduplication matters because a page can retry, rerender, or send the same logical event more than once. Without it, a chart can look precise while counting the wrong thing. I used validated ingestion and event identifiers to make the recorded journey more useful, then built the admin views on top of it.
The dashboard includes drop-offs, timing between steps, comparisons across periods, and customer journey views. I also worked on understanding which first-purchase categories led to repeat buyers. That connects catalogue decisions to customer behavior, rather than only comparing products by their immediate sales.
Attribution was another part of the picture. UTM history and server-side conversion events helped connect acquisition to orders. Those signals still need careful interpretation; an attribution field is not proof that one campaign caused a purchase. The practical value is giving the team a consistent view to investigate and compare. Meta tracking was later disabled in the archived storefront, so I describe it here as implemented work, not a service that is still running there.
05 / OpenAI Agents SDK · tool calling · file search · MongoDB sessions
An AI shopping assistant that can help you choose.
Describe your car, your style, and your budget. The assistant can find matching products, continue the search, track an order, or help with a policy question.
I built an OpenAI Agents SDK flow with classification and specialized data-query, retrieval, direct-answer, and human-handoff paths. Product and order tools use the app’s data, while policy answers use document retrieval. MongoDB-backed sessions keep the conversation together with bounded context and summarization.
The screenshot below starts with a simple request: a red car and a budget. The response is a gallery of real products, with images, prices, discounts, and links. I wanted the assistant to help someone make a choice inside the shopping experience, rather than send them back to search with a paragraph of generic advice.
I used the OpenAI Agents SDK to separate routing from the work itself. A classifier returns a Zod-validated category and extracted intent. Product discovery goes to a data-query agent with typed tools. Returns, installation, and care questions go to document retrieval. Simpler questions can take a direct-answer path, and requests for help from a person can hand off to WhatsApp support.
The product-search tool accepts a query, keywords, category, price range, sort order, and page. Its result carries catalogue data such as images, prices, availability, and product links. The interface can render those results as cards. That connection matters: a recommendation should lead to a product the customer can actually inspect.
Follow-up requests need state too. When someone says show more, the assistant should continue the previous search with the same budget and filters. I carried pagination state and search context through the conversation, including the current page and whether more results exist. The client also handles common continuation requests without making the customer repeat everything.
Order support uses a different tool that reads the order state, delivery estimate, tracking link, and progress steps. Policy answers use file search over the knowledge base. Keeping those sources separate helped avoid treating a product recommendation, an order status, and a return rule as the same kind of answer.
Shopping questions do not all need the same answer path. Finding a product, checking an order, explaining a policy, and asking for a person are different jobs. I built an agent flow that classifies the request and routes it to a suitable path instead of asking one prompt to improvise everything.
The data-query path can use product and order tools. Policy questions can use document retrieval. A direct-answer path handles simpler responses, and a handoff path can point the customer toward human support. MongoDB-backed sessions preserve the conversation, while bounded context and summarization keep the history from growing without limit.
The useful part of an assistant like this is its connection to the product. A fluent answer is not enough if it cannot find the right catalogue item or if it invents an order status. That experience informed how I later thought about AI tools at Blitzit: model output and application authority should be separate responsibilities.
06 / Vercel · AWS S3 / CloudFront · connection pooling · caching
Engineering with the business in mind.
The best technical decision is the one the team can operate and afford.
I worked on image caching, serverless database pools, catalogue feeds, API reliability, and deployment costs as the product evolved. Owning the application code meant owning its maintenance and operating costs too. Building it taught me to balance control, maintenance, and the next stage of the business.
Owning the platform also meant watching the less visible costs. Images, serverless database connections, catalogue generation, and repeated API work all affect the bill and the reliability of the app. I worked on caching, connection pools, image delivery, and deployment behavior as the product changed.
The main domain eventually moved to Shopify. The business had reached a different point, where maintenance effort and infrastructure predictability carried more weight. Building the custom platform gave me a practical understanding of that tradeoff: flexibility is valuable, but someone has to keep every part working.
The original Next.js storefront and admin platform are the work shown here. They connected the customer’s choices to the team’s daily operations, and gave me experience with the full lifecycle of a commerce product.
What I take from it.
MaddyCustom made engineering feel very concrete. A wrong quantity could affect production. An unclear payment state could become a support conversation. A useful admin screen could save the team from checking several systems manually. That feedback changed how I judge a feature.
I came away with experience across the full product loop: deciding what needs to exist, building the customer interaction, connecting the backend, and supporting the people who operate it. The strongest part of the work is not a particular framework. It is that those pieces were built to serve the same business.
Business figures are reported outcomes, not measurements from the demo. The main domain later moved to Shopify. This case study covers the original custom-built storefront and admin platform.
Revenue shown in USD using the October 1, 2026 reference rate. Conversion source ↗