← Blog

Customer job submission for a small print farm

Intake sits in front of the library, not inside: an email or a file drop, a rule for files to reach production. What each channel costs and where the job ends.

print-farmworkflowguideapi

The intake workflow that survives contact with customers is duller than the one people ask for. An email address, one place every incoming file lands, and a rule that nothing gets sliced before it has an order number. Most farms outgrow the spreadsheet long before they outgrow that shape.

Worth saying early, because it decides the architecture: PrintStash has no customer-facing upload page. Every write path in its API requires an account, and a share link is a read-only GET. So intake is something you build in front of the library, and the library’s job starts at the moment a file becomes a model you can slice, print, and cost.

Decide what a submission is incomplete without

Pick the fields before the channel, because the channel is just a way of collecting them. A submission you can actually quote needs the contact, the files, the material and color, the quantity, the deadline, and one sentence about what the part has to fit or survive. That last one prevents most of the rework: a bracket that has to bolt to something needs different walls than a display piece, and customers rarely volunteer the difference.

Two fields people forget are the ones that cost money later. Ask who pays for a reprint if the part is dimensionally fine but the customer expected otherwise, and ask whether they have printed this before, because a file that failed on someone else’s machine usually fails on yours for the same reason.

Start with an email address

An address like print@yourfarm.example and a reply template covers more volume than expected. The template is the form: same questions, same order, every time, so a reply either answers them or is visibly incomplete.

What breaks first is not the volume, it is the attachments. Mail servers commonly cap messages in the 25 MB range, which a single detailed STL clears without trying, and customers respond by sending a Google Drive link that expires or a WeTransfer link that expires faster. When you start downloading files from three different people’s cloud accounts, move to the next option.

Use a file drop when attachments stop working

A self-hosted upload target fixes both the size limit and the link rot. Nextcloud’s File drop share type is the closest thing to a purpose-built answer: the recipient can upload into a folder without seeing what is already in it, so one link per customer or one per order keeps submissions separated without creating accounts. Checked August 19, 2026.

Name the folders after the order, not the customer. 2026-0184-hendricks sorts, dedupes, and survives the customer who orders twelve times. If you already run Nextcloud for documents, this is close to free; if you do not, a generic form service with file upload works and costs you the privacy tradeoff of parking customer files on someone else’s server before you have looked at them.

Discord, if your customers already live there

For a farm serving a community rather than businesses, a channel where people post an STL and a description is the intake with the highest response rate, because it is where those conversations already happen. It is also the one with the worst record: threads scroll, files attached to Discord messages are not a place to keep anything, and there is no state on a request beyond whether you remembered to reply.

Treat it as a front door rather than a queue. Answer in the channel, then move the files and the requirements into whatever holds the order before you commit to it. A bot that copies attachments into a folder on submission is a small amount of code and removes the worst of the risk, which is a customer’s only copy of a file living in a chat log.

Getting files into the library

Once files land somewhere you control, they need to become models. By hand, that is an upload with a collection and tags set at the same time. Collections are the permission boundary in PrintStash, so a collection per customer makes sense when you have a handful of repeat clients and someone else needs access to only their files. Beyond that, one customer-work collection with a tag per client scales better than a tree with forty branches, and the makerspace setup covers how the View, Edit, and Admin roles behave once more than one person is involved.

If files arrive in a folder, a script beats clicking. Create a named API key under Settings -> Access for an account that has edit access to the intake collection and nothing else, exchange it for a token, and post each file to the ingest endpoint:

Terminal window
TOKEN=$(curl -s -X POST http://localhost:3000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"intake","api_key":"<api-key>"}' | jq -r .access_token)
curl -X POST http://localhost:3000/api/v1/ingest/model \
-H "Authorization: Bearer $TOKEN" \
-F "file=@2026-0184-bracket.stl" \
-F "model_name=2026-0184 mounting bracket" \
-F "tags=customer-work,hendricks,2026-0184"

Ingest is asynchronous and returns a job ID, so a script that needs to report success polls GET /api/v1/ingest/jobs/{job_id} rather than assuming the upload finished the work. Using the API for automation has the rest of the endpoints and the reason an API key is a login credential rather than a Bearer token.

The other route is to skip uploading entirely. Point a shared volume at the folder your file drop writes into, and PrintStash indexes new submissions where they already are, on a schedule. That keeps the customer’s original bytes exactly where your backup policy already covers them, at the cost of a scan interval between the upload and the file appearing.

Tracking a job from order to outcome

This is the part the library is actually for. The order’s model holds the customer’s mesh as the source, each slice attached as a G-code revision with a label saying why it exists, and one revision marked as recommended once it prints correctly. On a reorder, that recommended revision is the answer to “what did we run last time”, which is otherwise a question about a filename in a folder from four months ago.

Print outcomes go on the revision, not the order: needs_test while you are dialing it in, known_good once it works, failed on the attempt that did not. Each print job records its cost, frozen at completion, so a filament price change next quarter does not rewrite what this order actually cost you. With Spoolman connected the cost uses the real spool price rather than a material preset, and measured filament use and duration come back from Moonraker only; the beta providers fall back to slicer estimates, which the compatibility matrix breaks down per provider. For quoting, that distinction matters more than it looks: an estimate is fine for the quote and wrong for the margin.

For dispatch, enqueue the recommended revision instead of sending it to a named machine and let the fleet queue place it, with manual, default-printer, or least-busy routing depending on how much control you want.

Sending a preview back for approval

Before printing forty of something, most disagreements are cheaper to settle on a screen. Create a share link on the model with downloads off, and the customer gets a read-only page with the preview, the revision labels, and the parsed settings, without an account and without access to anything else in the library. Set an expiry that matches the conversation and revoke it when the order closes. Expiring share links covers what the page does and does not expose.

Downloads off is the setting worth defaulting to for customer-supplied work, because a link that hands back a source mesh is a link that can be forwarded.

What this does not give you

No part of the above is an order system. PrintStash has no table for a customer, a quote, an invoice, a purchase order, or a payment, and a collection named after a client is not a customer record. Order state lives in your spreadsheet, your accounting software, or an actual ERP, and whether a small farm needs one is a real threshold that arrives later than the printer count usually quoted at you.

So the division to build around is that intake and money happen outside the library and production happens inside it. Pick the intake channel that matches how your customers already contact you, and keep the library as the thing that can answer which file printed and what it cost.

Questions that come up

Can customers upload straight into PrintStash?

No. There is no anonymous or customer-facing upload endpoint, and every write route in the API requires an authenticated account. Share links are read-only and expose one model. The practical pattern is a separate upload target such as a Nextcloud file drop or a form, then either a person or a script moving files into the library, or a shared volume indexing the drop folder in place so the files never need to be copied at all.

Should each customer get a collection or a tag?

Collections when access control matters, tags otherwise. A collection is where PrintStash’s View, Edit, and Admin permissions attach, so a customer or a subcontractor who should see only their own files needs one. If nobody outside the farm ever logs in, a single collection for customer work plus a tag per client stays much easier to browse than dozens of near-empty collections, and tags can be combined in a filter where a collection tree cannot.

How do I know what an order actually cost rather than what I quoted?

Each print job stores its cost at completion, so the number does not move when filament prices change later. How accurate it is depends on the printer: Moonraker reports measured duration and filament use, while the beta providers report slicer estimates, and an estimate that assumed no failures will understate a job that took three attempts. Log the failed attempts as prints too, otherwise the order looks cheaper than it was and the next quote inherits the error.

What is the smallest setup worth building?

An email address, a folder per order on storage you control, and a library where the customer’s mesh and the slice that worked live on the same model. That covers the two questions that actually cost money when unanswered: which file is the current one, and what happened last time you printed it. Add a form, a bot, or an ERP when you can point at the specific thing that broke, not before.

Can I automate intake end to end?

Partly. The upload side automates cleanly, since a script can watch a folder and post to the ingest endpoint with an API key, and shared volumes remove even that step. What does not automate is judgment: deciding whether a mesh is printable, whether the requested material suits the part, and what the job should cost. Those are the steps where a farm earns its margin, and a pipeline that skips them mostly automates the production of bad quotes.

Sources

  • Nextcloud file sharing documentation for the File drop share type. Checked August 19, 2026.
  • PrintStash ingest, share, and fleet routes at the shipped v0.11.4 release in the repository, for the authentication requirement on write paths and the read-only share endpoints.
  • PrintStash API reference and compatibility matrix for endpoint detail and per-provider measured values.