Automation · Inventory transfers

The order builds itself from the attachment

Every operation that moves inventory on somebody else's behalf ends up with a request template. What it does not end up with is one request template. It ends up with a template, and then with every requesting team's export of that template, and then with a person who opens each one and types it back in. Taking that person out of the loop is everyday work in 3PL software for inventory held on a client's behalf.

An estimated $33,348 a year in labour time saved, by the site's own tracker. Where the figure comes from

7 request types on one page
2 workbook formats accepted
5 tracked upload states
Confidential client & operation

What it saves

$33,348 a year, estimated

The site's efficiency-savings tracker timed eight tasks by hand and again with the software. The labour time saved comes to an estimated $33,348 a year.

TaskSaved a year
Tracking inbound overages$11,514
Transfer orders for one program$6,494
XML files for another system$5,513
In-transit records, second program$3,136
In-transit records, first program$2,948
Virtual transfer orders, built and uploaded$1,425
Lease-return orders$1,169
Inventory adjustment requests$1,149
Total$33,348

Estimated by the site's own efficiency-savings tracker: each task timed by hand and again with the software, the time saved valued at a fully loaded labour rate over a 252-day working year. Labour time only, to the whole dollar.

Built for a contract-logistics operation that moves inventory between owners, dispositions and facilities on request from several teams, on a customer-owned platform.

An emailed spreadsheet becomes a validated, reference-numbered transfer orderInventory transfer requests arrive as spreadsheets attached to email in three shapes: the template as issued, an HTML table saved with a workbook extension, and a template with extra columns and a duplicate header. A nine-stage pipeline reads the mailbox, parses each attachment in isolation, checks every line against live available balance at its source disposition, groups the balanced lines, splits them into even unit bands, reserves each reference number in the database before use, builds the order file, uploads it and records one of five upload states. Lines that do not balance are dropped and reported in the run summary. A message is marked processed before anything is uploaded, one lock covers the scheduled run and the check-now button, and the whole flow has one off switch that defaults to off if its configuration fails to load.MAILBOX · ATTACHMENTSWorkbookthe template as issued; 2workbook formats areacceptedHTML tablesaved with a workbookextension: read as aworkbook first, as HTMLon parse failure, never bythe extensionAltered templatetwo extra columns and aduplicate header:columns are taken byposition and trailingextras ignored7request types on one page2workbook formats accepted5tracked upload statesWHAT WE BUILTTransfer pipelineon a schedule and on demand, behind one lockRead the mailboxpull the attachments off each messageParse each attachment in isolationan unreadable file costs the run only itselfCheck every line against live balanceat its source disposition: unbalanced lines aredropped and reported, the rest still goGroup the balanced linesby owner, source disposition, destination andrequest typeSplit into unit bandsa group over the ceiling becomes two evenhalves; a straddling line is divided across bothReserve each reference numberin the database before it is usedBuild the order fileline numbers renumbered inside each referenceUploadone portal session on one accountRecord the state5 tracked upload states, never an empty reasonWHAT COMES OUT · SAMPLE VALUESRun summary, emaileddropped lines and the built workbook,whether or not something failedDropped lines, reportedreason: balancethe stock had already moved at itssource disposition; the requester is toldValidated orderREF 0417uploadedone owner, source disposition, destination andrequest type, inside the receiver's unit bandValidated orderREF 0418not uploadableno reference for one group: it keeps therequester's reference, unsplit, and says whyA message is marked processed before anything is uploaded, so a crash leaves a sheet visibly unfinished rather than a second copy of the order. Onelock is shared by the scheduled run and the page's check-now button. The whole flow has a single off switch, and if the configuration it reads fails toload it falls back to off, never to on.
Three shapes of emailed attachment go in; validated, reference-numbered orders come out, with the lines that failed the balance check reported beside them. References and card values are illustrative.

How do you turn emailed Excel transfer requests into warehouse orders?

Read the attachment defensively and check every line before an order exists. In this build a scheduled pipeline reads the mailbox, opens each attachment as a workbook or, failing that, as an HTML table, checks every line against live available balance, groups and splits the survivors into reference-numbered orders and uploads them, recording what happened to each sheet.

The problem

Requests to move inventory — between owners, between dispositions, out to a repair line, back off a lease — arrived the way inter-company work usually arrives: a spreadsheet attached to an email. Someone opened it, read the lines, and built the matching order by hand, one part number at a time, whether it was inventory moved between owners or stock changing disposition on the same shelf.

The format was the first difficulty. Every requesting team exported from something different, so the same template came back with columns bolted on the end, with two columns carrying the same header, and — often enough to matter — as an HTML table saved with a spreadsheet extension, which opens perfectly well by hand and defeats any reader that trusts the file name.

The second difficulty was that nothing was checked before the order existed. A request against stock that had already moved became an order nobody could fill, found at the far end of the process by somebody with no idea where it came from.

  • No two requesting teams sent the same file, and the shape changed without notice
  • Availability was confirmed after the order was built, if at all
  • Reference numbers came off a shared running list — fine until two people work at once
  • Nothing connected a finished order back to the message that asked for it

What we built

One page with a tab per request type, and behind it a pipeline that runs on a schedule and on demand: read the mailbox, pull the attachments, check every line, build the order file, upload it, record what happened. In short, emailed forms turned into structured records, and then into orders.

The reading end assumes nothing about the sender, because the sender is not going to change.

  • An attachment is read as a workbook first and as an HTML table second — the fallback triggers on the parse failure, never on the file extension
  • Columns are taken by position, since the template carries two columns under the same name; trailing columns nobody asked for are ignored, and a file with too few columns is an error rather than a guess
  • Every line is checked against live available balance at its source disposition before it can become an order; lines that don't balance are dropped and reported, and the lines that do balance still go
  • Balanced lines are grouped by owner, source disposition, destination and request type, then split so every reference lands inside the unit band the receiving process accepts — a group slightly over the ceiling becomes two even halves, not one full reference and one nearly empty one
  • A line whose quantity straddles a split is divided across the two references rather than rounded onto either side of it
  • Each reference number is reserved in the database before it is used, so two runs on the same day cannot issue the same one, and line numbers are renumbered inside each reference

Keyed by hand or built from the attachment

StepKeyed by hand (before)Built from the attachment (after)
Reading the requestSomeone opened each attachment and read the linesRead as a workbook, or as an HTML table when that parse fails
Checking stockAfter the order was built, if at allEvery line against live available balance first
Reference numbersTaken from a shared running listReserved in the database before use
Tracing an orderNothing linked it to the message that asked for itMessage, sheet, references and dropped lines on record

Nothing half-built

The expensive failure in a pipeline like this is not a crash. It is a partial success that looks finished — the same orders created twice because a run was repeated, or a sheet sent up carrying a reference somebody else is also using.

So the order of operations is deliberate, because the goal is orders that are safe to retry. A message is marked processed before anything is uploaded: if the upload dies, the sheet is left visibly unfinished for a person to push through, which is a smaller problem than a second copy of the same order appearing downstream. Each attachment is processed in isolation, so one unreadable file cannot cost the run the work it has already done.

Where the build cannot be completed correctly it is not completed at all. If a reference can't be generated for a group — an unrecognized request type, a daily sequence run out — that group keeps the requester's original reference, unsplit, and the whole sheet is marked not-uploadable with the reason attached. Nothing invents a number to keep moving.

  • One lock is shared by the scheduled run and the page's check-now button, so pressing it mid-run cannot process the same message twice or open a second portal session on one account
  • The whole flow has a single off switch that reads no mail, builds nothing and uploads nothing — and if the configuration it reads fails to load, it falls back to the environment rather than defaulting to on
  • Every sheet carries its own upload state and reason, so the page shows what happened instead of implying it
  • A failed upload never records an empty reason: the exception class is always kept, because blank is indistinguishable from never attempted
  • Any run that processed an attachment emails a summary with the dropped lines and the built workbook, whether or not something failed

The result

A request goes from an inbox to a live order without anyone retyping it, and the people who used to key them spend their attention on the exceptions the checks surface — which is the part that needed a person all along. The balance check is the simplest form of multi-node inventory and allocation rules: nothing becomes an order unless the stock is there.

The audit trail is the quieter win. For any order the system built you can name the message it came from, the sheet it was written to, the references it was split into, and every line dropped along the way with the balance that dropped it. When a requester asks why their transfer came back short, the answer is on the page rather than in somebody's mail folder. Requests that arrive as PDFs or scans rather than workbooks are the case for LLM document and email automation; these arrived as spreadsheets, so a plain parser was the right tool.

Questions about this build

Why read a spreadsheet attachment as an HTML table?

Because requesting teams often send an HTML table saved with a spreadsheet extension. It opens perfectly well by hand and defeats any reader that trusts the file name. So each attachment is read as a workbook first and as an HTML table second, and the fallback triggers on the parse failure, never on the file extension.

What happens to transfer lines that fail the balance check?

They are dropped and reported, and the lines that do balance still go. Every line is checked against live available balance at its source disposition before it can become an order. Any run that processed an attachment emails a summary with the dropped lines and the built workbook, and the page keeps the balance that dropped each line, so why a transfer came back short is on record.

How are duplicate orders prevented when a run repeats?

By the order of operations. A message is marked processed before anything is uploaded, so a failed upload leaves the sheet visibly unfinished rather than creating a second copy downstream. Each reference number is reserved in the database before use, and one lock is shared by the scheduled run and the page's check-now button.

Built with

  • Python
  • Flask
  • pandas
  • openpyxl
  • SQL Server
  • Gmail API
  • Selenium WebDriver
  • TOTP
  • Scheduled workers

On the details we left out

Client, location, system names and volumes are withheld deliberately; the savings above are the tracker's dollar estimates, with the minutes, rates and headcount behind them left out. We're happy to go deeper on architecture and approach on a call.

Services this draws on

Workload Optimization

Squeeze more throughput out of the hardware and licenses you already pay for.

See Workload Optimization →

System Integration

Connect aging systems to modern APIs, data platforms, and SaaS tools.

See System Integration →

3PL & Logistics Systems

WMS, TMS, and warehouse-floor workflows for 3PLs, brokers, and distributors.

See 3PL & Logistics Systems →

More projects

Integration · Inbound logistics

Knowing where inbound freight actually is

Inbound shipments were tracked by opening carrier websites one tab at a time. We replaced the tabs with live carrier integrations and a workflow that routes exceptions to the person who can fix them.

Read the story →
Automation · Warehouse systems

Automating a system that has no API

The warehouse platform offered no write interface, so every transaction went in by hand. We automated the screens themselves — carefully enough to trust with live inventory.

Read the story →
Planning · Outbound fulfilment

Turning a flat order backlog into a plan the floor can pick

A daily backlog feed said what had been ordered — not what could be built, released or picked. We built the pipeline that turns it into a wave plan, a queued pick order, and a screen that says where each one got to.

Read the story →
Get in touch

Got requests arriving as attachments?

Forward one request, names removed, and say how many arrive in a week. Talk to an engineer.

Prefer to talk?

Call 832-598-8234 or email msco@stoneagesoftware.com. Houston, Texas — serving Houston, The Woodlands, Conroe, Sugar Land, Katy, Pearland, and the Greater Houston metro.

What happens next

We read your message, an engineer replies with questions or a straight answer, and if a call would help, we book one. From the first reply on, you're talking to the people who'd do the work.

No obligation — we reply within one business day. This form sends your details to Stone Age Software LLC so we can respond to you. See our privacy policy for how we handle them. This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.