Field Notes · B2B Integration

Eight years running a B2B distribution system

I built a system that pushed EDI 832 price catalogs out of a manufacturer’s ERP and into its dealers’ purchasing software over FTP. Then I hosted it on Google Cloud and ran it for eight years, until the client changed direction in 2026 and I shut it down. This is the honest version — what I built, what I rebuilt, and what operating your own integration for eight years actually teaches you.

In plain English

A stone and tile supplier needed its price lists to show up automatically inside the software its dealers use to buy. That is a machine-to-machine problem, not a website problem. I wrote the software that turns their prices into the industry’s standard electronic catalog file, drops it in a private folder for each dealer, and lets each dealer’s system come pick it up. Then I kept that running, every week, for eight years.

Dealers don’t visit your website

The thing I had to understand before I could design anything: in the floor covering trade, a dealer never goes shopping on a supplier’s site. They live inside their own business-management platform — RFMS is the big one — and they expect vendor catalogs and price agreements to simply be there, already loaded, already current.

The industry settled that with fcB2B, layered on EDI X12 v4010. The document that matters is the 832 Product/Price Catalog. The supplier writes an 832 into a private FTP folder belonging to one dealer; the dealer’s system polls that folder on its own schedule — RFMS every two hours — and ingests it. Nobody sits in the middle. There is no API to call, no acknowledgment to negotiate, and nobody to phone when your segment terminators are wrong. Your file is either right or the dealer quotes a bad price.

My client was outside that network entirely, moving price agreements by spreadsheet, phone, and fax. Getting on it wasn’t a nice-to-have; it was the price of admission to the buying groups their dealers belonged to.

Version one: a desktop app and a very careful FTP server

I started where the business actually was. Version one was a WPF desktop application in C# over a MariaDB catalog, running against a Linux box on Google Cloud. Load a price list, adjust what needs adjusting, pick a customer, click once — and the app emitted a conforming X12 4010 832 and delivered it to that dealer’s FTP folder.

Writing the 832 generator was the part people assume is hard, and it was finite: ISA and GS envelopes, a BCT header, N1 and REF segments identifying the trading partners, then a LIN/PID/MEA/CTP/SLN loop per item carrying SKU, description, material code, unit of measure, and price. You get it right once, you validate against the spec, and it stays right.

What actually consumed the calendar was the trading-partner estate. Every dealer needed an isolated FTP account with its own credentials, its own inbox and outbox directories, correct ownership and permissions on both, and account numbers and sender/receiver IDs that matched what the dealer’s system was configured to expect. Every one of those is a small, boring, entirely manual coordination with a third party. I ended up running dealer onboarding as a standing service for the life of the system, because that work never stops — my client kept selling.

Then the ERP moved, and the design was wrong

A few years in, the client centralized their operation on NetSuite. That invalidated my architecture, and it took me a while to admit it. Pricing now lived in the ERP — base pricing, buying-group levels, fabricator levels, plus customer-specific special pricing — and my desktop app had become step three of a five-step manual relay: build the list in NetSuite, export to Excel, import to my app, hand-edit, transmit.

The real damage wasn’t the labor. It was that anyone in the company could change a price in NetSuite and nothing downstream knew. The transmitted catalogs silently drifted away from the system of record, and the only detection mechanism was a dealer complaining. When two systems can both write the same fact, you don’t have an integration; you have two truths and a race.

So I rebuilt it. An Angular single-page app replaced the desktop client, on an ASP.NET Core Web API whose TypeScript client I generated straight from the OpenAPI contract — a small discipline that meant the front end and back end could never quietly disagree about a payload shape. A dedicated integration library talked to NetSuite over SuiteTalk web services, flattening item searches and pricing joins into one normalized catalog record: SKU, sales description, manufacturer, material code, product group, price level, unit price.

The bigger shift was that the system stopped being a program someone drives and became a set of scheduled workers. Three sync jobs pulled customers, items, and price lists from NetSuite into MariaDB twice a day. An FTP queue processor woke up every minute, drained the transmission queue, generated each 832, and delivered it. Docker and Docker Compose packaged the whole thing, images landed in Google Cloud Container Registry, and nginx sat in front of both the Angular app and the API on the cloud VM.

flowchart TD
    NS["NetSuite ERP -- system of record for pricing"]
    SYNC["Sync workers -- customers, items, price lists -- cron twice daily"]
    DB[("MariaDB catalog and queue")]
    UI["Angular SPA -- pick a customer, transmit"]
    API["ASP.NET Core Web API -- OpenAPI contract"]
    Q["FTP queue processor -- cron every minute"]
    GEN["832 generator -- X12 4010"]
    FTP["Per-dealer FTP folders -- isolated credentials"]
    RFMS["Dealer system polls every 2 hours"]

    NS --> SYNC --> DB
    UI --> API --> DB
    API --> Q
    DB --> Q
    Q --> GEN --> FTP --> RFMS
                    
One writer of pricing truth, one queue, one delivery path — everything else is scheduling.

The best feature I shipped was a deletion

Version two removed price editing entirely. Version one let a merchandiser tune numbers after import, and that capability was the whole reason catalogs drifted. Once NetSuite was the source of truth, a second system that could mutate prices wasn’t a convenience — it was the defect. Taking the feature out closed the bug class permanently, and it was a genuinely uncomfortable conversation to have with a client who liked that feature.

I folded catalog line de-duplication into 832 generation at the same time, which fixed the one kind of malformed output version one had been shipping. Two changes, both subtractive, both worth more than anything I added.

What eight years of operating it taught me

  • Owning the hosting changes how you design. I wasn’t handing this to an ops team — the Google Cloud VM, MariaDB, the FTP estate, nginx, the cron fleet, and the pager were all mine. You write very different code when you know exactly who gets woken up by it.
  • Cron is underrated. A one-minute queue drain and twice-daily ERP syncs carried this system for years. There was never a workload here that justified a message broker, and adding one would have bought complexity I’d have paid for every year.
  • You don’t control the other end. Trading partners upgrade, change credentials, move IDs, and re-onboard without telling you. A B2B integration is a long-running relationship with systems you can’t see, and the error handling has to assume the far side is unreliable by default.
  • Onboarding is the product. Every new dealer was a provisioning task, not a config toggle. Treating that as a first-class service — instead of a favor squeezed between projects — is what made the system commercially useful to my client.
  • ERPs move under you. NetSuite versioned, the product line changed, price levels came and went. Eight years means the integration is never finished; it’s maintained.
  • Silence is the metric. The system was successful in the years nobody mentioned it. That’s a hard thing to put on a slide, and it’s the actual goal for infrastructure like this.

Shutting it down

In 2026 the client took their commerce strategy a different direction, and the system came down. I don’t think of that as a failed ending. It ran in continuous production for eight years, retired a spreadsheet-and-fax process, and put a small manufacturer on a network its competitors were already on. Very little software I’ve written has had that long a useful life, and knowing how to decommission something cleanly is part of having built it.

Stack

EDI X12 4010 832 Product/Price Catalog fcB2B NetSuite / SuiteTalk Google Cloud C# / .NET ASP.NET Core WPF Angular / TypeScript MariaDB Docker / Compose Container Registry nginx Linux / cron vsftpd OpenAPI / Swagger

Got an integration that has to outlive the project

I build the kind of systems someone still has to be running in year eight — and I’m usually that someone.