Last Tuesday I ran my upload batch twice. Same folder, same CSV, two hours apart, because I could not remember which of the 40 files I had already pushed to Adobe. Adobe does not punish an accidental duplicate with a warning. It counts against your review standing, and repeated resubmission of already-published assets is one of the listed reasons reviewers flag a contributor account. I caught it before the second batch went through, but only because I opened my upload history and scrolled. That was the evening I decided the tracking had to be a script, not my memory.
Here is the honest version of what I learned, and the system I now run in one evening a week.
The CSV header is a hard gate, not a suggestion
All three major stock channels reject a metadata file with a wrong header instantly. Not a soft warning, a rejection at upload. Adobe expects a 5-column format, Vecteezy expects 4 columns, Dreamstime wants 15. If you hand-build these in a spreadsheet, you will eventually paste a column in the wrong order or save with the wrong delimiter, and the whole batch bounces.
So make_metadata.py writes exactly the header each platform demands and nothing else. You point it at a folder of files, pick the platform, and get a CSV that passes the first check. It also has a fallback: if you do not supply a titles file, it title-cases the filename. That is fine for a first pass, but I replace those titles before submitting, because reviewers do read them.
The time math changed when I stopped hand-editing spreadsheets. Building three platform CSVs for a 40-asset batch used to take me most of an evening. Now it is three commands and about a minute of compute.
Double-posting is the fastest way to get flagged
This is the part I got lucky on. Running a batch twice against the same channel is the classic mistake for anyone automating uploads. The stock sites treat duplicate submissions as a quality problem, and enough of them gets your account reviewed or restricted.
My fix is dumb-simple and that is why it works. upload_tracker.py is a plain ledger. Before a batch I run pending against my items file to see what is not done. After each successful upload I mark it done. The ledger is a text file I can read, back up, and grep. A re-run of the batch script checks the ledger first, so double-posting is impossible rather than unlikely.
The lesson for anyone building this: do not build a database. Build a list you cannot misread. My whole upload history is one file I can open in Notepad.
One process at a time on a 2 GB box
The entire pipeline runs on 1 vCPU, 2 GB RAM, no GPU. That constraint shaped every habit in the system.
Never fan out image jobs in parallel on a single core. It does not go faster, it goes slower and the box falls over. I call gc.collect() after each asset to free memory between items. For heavy render steps I process one file per subprocess so memory actually returns to the OS instead of pooling inside Python. And I measure peak RSS, not average, with resource.getrusage(RUSAGE_SELF).ru_maxrss, because average tells you nothing about the moment the job dies.
One more that saved real money: render at the resolution you need. Inspecting a preview at 60 dpi instead of 300 means the check costs almost nothing, and I only pay full resolution for final output. With AI image generation priced per image, re-rendering a whole batch because a check failed at high resolution is a real cost, not a theoretical one.
Packaging is a command, not a craft session
A bare file does not sell. A zip with a real cover at 1280x720 and a thumbnail at 600x600 does. I used to spend 30 to 45 minutes per product in an image editor making covers, which for 39 products is a full working week spread across evenings.
pack_product.py takes a folder, a title, a subtitle, and an item count, and writes the zip, the cover, and the thumbnail. The only third-party package involved is Pillow. Covers are not art, they are consistent framing, and consistency is exactly what scripts are good at.
Same for documents. md2pdf.py turns a Markdown file into a styled PDF with headings, tables, lists, and links, using reportlab. One command. The docs look like products because the styling is fixed, not because I have taste at 11pm.
The folder layout matters more than the tools
Without a fixed structure a catalogue becomes a junk drawer. Mine is five folders: assets for raw output, output for ready-to-submit files and metadata, packs for finished zips and covers, research for notes and scrape logs, scripts for the tools.
The one I got wrong early: I deleted research during a cleanup pass. Those notes and logs are the raw material for future products. I rebuilt what I could and now nothing in that folder ever gets removed.
What a week actually looks like now
One evening, roughly three hours. I generate and review the week's assets, run the three metadata commands, check the ledger with pending, package two or three products, and mark everything done. The scripts have packaged 39 products so far, about 856 MB of output, all on that 2 GB server. The whole parent pipeline is around 19,000 lines, but the packaging layer you need daily is four scripts and two pip installs.
I am not going to pretend this is a business on autopilot. Reviewers still reject things, titles still need rewriting, and some weeks I skip the evening entirely. But the failure mode I hit last Tuesday, running a batch twice because I forgot, is now structurally impossible.
I bundled these four scripts, the folder layout, and the RAM notes into the pipeline starter kit, because the packaging layer is the boring part that stalls everyone and it should not take you a year to get right.
Building digital products? Free guides & kits for digital product sellers.
Top comments (0)