Email-to-ERP sales order automation
A wholesale distributor was receiving several hundred purchase orders a day as email attachments, and keying every one of them into NetSuite by hand.
- {{ m.k }}
- {{ m.v }}
Orders arrived in a shared mailbox in a dozen different layouts: supplier PDFs, spreadsheet attachments, and plain text in the body of the mail. Two people spent most of their morning transcribing them, which meant orders received after 11am were rarely entered the same day.
The cost wasn't only the hours. Transcription errors surfaced downstream as short shipments and credit notes, and nobody could say with confidence how long an order had been sitting unprocessed.
Rather than chase a parser that could handle every layout, we split the problem in two: an extraction service that gets a structured order out of the message, and a NetSuite-side integration that only ever accepts clean, validated payloads.
Anything the extractor is unsure about, whether an unknown item code, a price that doesn't match the contract or a customer it can't identify, is routed to an exception queue with the original email attached, so a human resolves it in context instead of re-keying it.
Orders now post within minutes of arriving, including overnight. The two people who were transcribing them spend their morning on the exception queue instead, which on a normal day is a handful of items.
MyKit is a production multi-tenant commerce platform built for the sports teamwear industry. Sportswear suppliers sign up as organisations, then spin up individual, time-limited online shops for each club they supply: a GAA club, a rugby team, a school. Parents and players order personalised kit (name, number, size) through the club's own branded storefront; the supplier manages every order, payment, stock movement and fulfilment step from one admin console.
The problem it solves: teamwear suppliers traditionally take orders via spreadsheets, WhatsApp groups and paper forms, then manually collate hundreds of personalised items per club. MyKit replaces that with a structured, self-service pipeline from shop launch to shipped parcel.
{{ e.body }}
Consultants and sales reps spend their week on client sites. Timesheets got written up on Friday from memory, receipts arrived in envelopes weeks late, and approvals waited for a manager to be at a desk. The standard NetSuite mobile experience covers none of the custom workflows the business actually runs on.
A native app would have meant a second identity system, a second permission model, an external server holding financial data, and an app store release for every change. None of that was proportionate to the problem — and none of it would survive being deployed into a second customer's account, where the workflows are different again.
One Suitelet does both jobs. A GET request returns the entire single-page app as an inline HTML string, with no CDN links and no framework. Every subsequent interaction is a POST carrying a JSON body with an action and a session token, and the same script routes it.
Permissions are data, not code. Each app role is a custom record holding a permission level per feature, and every one of the 51 routed actions declares the feature and minimum level it requires. Adding a feature means adding a row, not editing a script.
Reads use SuiteQL so that joins and aggregations cost one query instead of a loop of lookups; writes go through the record module. Every service function documents its governance cost in its module header.
That structure is what makes the app deployable rather than bespoke. Each feature is a self-contained page module with its own actions, records and permission entries, so an account gets only the add-ins it turns on and a custom add-in for one customer is a new module rather than a fork of the app.
The search feature sends a plain-English question to Claude along with a schema description, and runs the SuiteQL that comes back. That is only safe because the model is never trusted with the last word.
The schema it sees is assembled from the searchable tables the user's role is permitted, so the prompt itself cannot reference anything out of bounds. What comes back then passes four gates before it touches the database.
Every executed query is written to an audit log with the role that ran it and the SQL that ran. The same pattern drives receipt scanning: a photo goes to the vision model, and a structured expense line comes back for the user to confirm rather than accept.
NetSuite holds the projects, the tasks, the budgets and the time. It does not hold the plan. Who is doing what next Tuesday lived in a spreadsheet beside it, maintained by hand and out of date within a day.
Because the plan and the ledger never met, the same information was entered twice: once when a manager scheduled the work, again when the consultant wrote up their timesheet at the end of the week from memory.
One Suitelet serves five views off a ?view= parameter. Each view owns its own model building and rendering; a shared page shell wraps the same chrome around whatever comes back, so a new view is a new module rather than a branch in a growing script.
Planning is the only thing the dashboard writes. Dragging a person onto a task and a day creates an allocation on a custom record, and that record is the single source the board, the resource view and the timesheet sync all read from — packed the same way by one shared module, so the three can never disagree.
Everything else is read: SuiteQL for joins and aggregations, and N/search only for the handful of fields SuiteQL cannot reach. Hours land against a person's real working calendar, so leave and non-working days push work forward instead of absorbing it.
A plan that generates time entries is a plan that can quietly corrupt payroll and billing. The sync is therefore written to be reversible and unfailing: it never throws, and every rule below exists to protect something a person did by hand.
Writes are gated on the way in as well. The deployment runs as administrator, so before anything is saved the acting user must be an administrator or a listed project manager, and the employee being scheduled must report to that manager. Every write is logged with the user who made it.