I still have the screenshot. A Tuesday night in October, 11:40 pm, and Adobe Stock had rejected 14 of my uploads in one batch. The rejection reason on every single one was the same: malformed CSV. I had built 14 vectors, rendered them, renamed them, and then hand-typed a metadata spreadsheet at midnight with the wrong column order. Adobe's uploader checks the header row before it checks anything else, and it rejects the whole file the moment the columns don't match its exact 5-column format. Fourteen finished products, zero live, and my weeknight gone.
That night changed how I schedule my work. The problem was never that making products takes long. It's that the boring parts leak into the hours I only have on weekends, and then they sabotage the small windows I get on weeknights. Here is the split that fixed it.
Weekends are for batching, weeknights are for pasting
I make the actual assets on weekends. My vector prompt pack generates the raw output, and I run it in one long session: one process at a time, no parallel jobs. My server is a 1 vCPU box with 2 GB RAM and no GPU, so fanning out image jobs in parallel just thrashes the machine. I call gc.collect() after every asset and check resource.getrusage(RUSAGE_SELF).ru_maxrss to see my real peak memory, not the average, which lies.
The last full run produced 39 finished products, 856 MB of output, on that same tiny box. Nothing about it needs beefy hardware. It needs patience and one job at a time.
Then I stop. Everything that requires judgment, titles, keyword choices, which assets make the cut, happens on the weekend while I'm fresh.
The CSV header is a contract, not a suggestion
This is the part that cost me that Tuesday night. Adobe Stock wants a specific 5-column CSV. Vecteezy wants a 4-column CSV with its own header names. Dreamstime wants 15 columns. Three platforms, three different contracts, and every one of them rejects a wrong header instantly, before looking at a single image.
So I stopped hand-typing spreadsheets entirely. make_metadata.py reads a folder of files and writes the exact header each platform demands, and nothing else. It takes a --titles titles.json file if I've written real titles and keywords on the weekend. Without it, it title-cases the filename, which is fine for a draft but I always replace it before submitting, because a filename like geometric-pattern-04.jpg as a title reads exactly like what it is.
The weeknight upload now looks like this: run one command per platform, open the CSVs in the uploader, done. Three commands, maybe ten minutes including coffee.
The double-post problem nobody warns you about
The second fastest way to get flagged, after a malformed CSV, is running your batch twice. On a tired weeknight, with a re-run to fix one typo, it is embarrassingly easy to upload the same file twice. Platforms notice, and repeated duplicates look like spam behavior on your account.
upload_tracker.py is a plain ledger, standard library only, no install. python upload_tracker.py done "asset-001.jpg" marks an item, pending shows what is genuinely left, and a re-run can never double-post because the ledger remembers. It is the least interesting script I have and the one I would rebuild first if I lost everything.
Packaging is a product decision, not a chore
A bare zip sells badly. The same files with a real cover at 1280×720 and a square thumbnail at 600×600 sell noticeably better, because the thumbnail is the only thing a buyer sees in a search grid. pack_product.py bundles the zip and renders both images in one command. Pillow is the only dependency it needs.
For the docs side, md2pdf.py turns a Markdown file into a styled PDF through reportlab, headings, tables, lists, links. That is how my license pages and quick-start guides become part of the product instead of loose text files.
The folder layout is the real automation
I resisted this for months and then lost an afternoon to it once. Without a fixed structure, a catalogue becomes a junk drawer, and you re-do work you already did. The layout this pipeline assumes is boring on purpose:
project/
assets/ raw output
output/ ready-to-submit files + metadata.csv
packs/ finished zips + covers
research/ notes and sources
scripts/ the tools
One rule I learned the hard way: never delete research/. My scrape logs and notes are the raw material for the next product. I cleaned that folder once to "tidy up" and threw away weeks of groundwork. It will not happen again.
What a week actually looks like now
Saturday: generate and curate assets, write titles and keywords into titles.json, run the packaging commands. Maybe four focused hours.
Tuesday or Thursday: make_metadata.py for each platform, check upload_tracker.py status, upload through each site's web form, mark items done. Ten to fifteen minutes. If I skip a week, nothing rots, because the ledger and the folder structure hold state for me.
The parent project behind all of this is around 19,000 lines of pipeline code with 300+ pages of research behind the rules the scripts enforce, and everything here was validated on that same 1 vCPU, 2 GB RAM machine. No cloud account, no framework. Four standalone Python scripts, two third-party packages total, and the two scripts I use every single week need nothing but Python.
I packaged all four scripts, with the folder layout and the RAM notes, into the same kit I use myself. If any of this matches the bottleneck you are stuck at, it is here:
Building digital products? Free guides & kits for digital product sellers.
Top comments (0)