I still have the email saved. Rejection number 100 came in on a Tuesday, same boilerplate as the other 99: "quality issues" or "keyword relevance" or whatever template the reviewer picked that day. I had about 40 images queued and every single one came back rejected. I was ready to quit.
The embarrassing part is what it actually was. Not the art. Not the keywords. The CSV header on my upload file was wrong. One row of column names, and Adobe's uploader rejects the whole batch before a human ever sees it. A hundred rejections, and maybe a third of them were never even reviewed. The pipeline ate my files and spat back form letters.
Here is what I learned fixing it, in case you are in the same hole.
The header is a contract, not a suggestion
Each stock platform has its own upload CSV format, and each one rejects a wrong header instantly. Adobe wants 5 columns. Vecteezy wants 4. Dreamstime wants 15. If you hand-roll a spreadsheet and guess at the column names, you will get it wrong in some subtle way, an extra space, a renamed field, wrong column order, and the upload fails with an error message that does not tell you which column broke.
The fix was to stop hand-editing and generate the CSV from the folder of files directly. My make_metadata.py script walks an assets folder and writes the exact header each platform demands, nothing else. No extra columns, no creative additions. Run it three times, once per platform:
python make_metadata.py ./my-assets --platform adobe --out metadata.csv --ext .jpg
python make_metadata.py ./my-assets --platform vecteezy --out vecteezy.csv --ext .jpg
Rejections from metadata went to zero after that. The remaining rejections were actual quality calls, which is a different problem and one you can only fix by looking at what got approved versus what did not.
Double-posting is the fastest way to get flagged
Second thing that nearly killed the account: I re-ran a batch after a partial failure and posted duplicates. Stock platforms do not like that. Re-submitting the same file reads as spam behavior, and enough of it gets your account reviewed or worse.
The fix is a dumb little ledger. upload_tracker.py keeps a text file of what you have already posted:
python upload_tracker.py pending items.txt # what is NOT done yet
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
Every batch script checks the ledger before it acts. A re-run now skips finished items instead of reposting them. It is the least glamorous code I have written and it has saved me more account risk than anything else in the stack.
The whole thing ran on 1 vCPU and 2 GB of RAM
I did not have a GPU or a beefy box. The pipeline that turned one prompt engine into 39 finished products, 856 MB of output, ran on a single-core, 2 GB server. That forced habits that turned out to be the right habits anyway:
- One process at a time. Never fan out image jobs in parallel on a 1-core box. You just thrash.
- Call
gc.collect()after each asset. Free memory between items or the third image OOMs. - Measure peak RSS, not average.
resource.getrusage(RUSAGE_SELF).ru_maxrsstells you your real ceiling. - Render at the resolution you need. Inspecting a preview at 60 dpi instead of 300 cut my render memory massively.
If your pipeline works on a 2 GB box, it will work anywhere.
Packaging is half the product
A raw file in a zip does not sell. What moved the needle was giving every product a real cover (1280x720) and a thumbnail (600x600), plus turning written content into an actual styled PDF with headings and tables. pack_product.py does the bundling and covers, md2pdf.py does the PDF, one command each. Two third-party packages total across the whole thing: Pillow and reportlab. The metadata and tracker scripts run on the standard library alone, no install at all.
Also, keep your research/ folder forever. I deleted my notes once to "clean up" and lost the raw material for three products I never rebuilt. Scrape logs and past notes are inventory, not clutter.
What a hundred rejections actually bought me
The rejections were not a signal that the art was bad. They were a signal that the pipeline around the art was sloppy: wrong headers, duplicates, no tracking, no packaging. Fixing the plumbing got the real signal back. Once the metadata was right, approvals started landing and I could finally tell which images genuinely needed work.
I packaged the scripts that did this into the Pipeline Starter Kit: the four tools above, the folder layout, the RAM notes, plus the install and quick-start commands, all validated on that same 1 vCPU / 2 GB box. It is the tool built from this work, not a course about it.
Building digital products? Free guides & kits for digital product sellers.
Top comments (0)