I still have the rejection email open in a browser tab from last March. I had batch-generated 40 vectors, built my Adobe Stock CSV by hand in a spreadsheet, uploaded everything in one sitting, and then, because the batch script had a bug and I didn't trust it, I re-ran the upload the next morning "just to be safe." Same 40 files, same CSV, two submissions each. Adobe's review caught it. That kind of duplicate submission is exactly the pattern that gets a contributor account flagged, and flagged accounts mean your whole queue sits in review limbo while you wait for a human to look at you.
The fix took me one evening. It is a ledger of about 60 lines of Python that remembers what I have already posted. It has since run across every batch I've made, and it is part of the pipeline starter kit. Here is what I learned building it and why it matters more than any prompt trick.
Double-posting is the cheapest mistake to prevent
Stock platforms do not forgive duplicates. Adobe, Vecteezy, and Dreamstime all match on file content and metadata, and a re-submission of the same asset reads as either spam or a terms problem depending on the reviewer's mood. When you run a batch at 2am and the script crashes halfway, you have no reliable memory of what went out. A spreadsheet doesn't count. Your memory doesn't count.
upload_tracker.py is the whole answer. It stores one line per asset: filename, status, timestamp. Three commands:
python upload_tracker.py pending items.txt
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
Before any upload, I run pending and only submit what it lists. The script uses nothing but the Python standard library, so there is no install step, no dependency drift, nothing to break on the server. Sixty lines, standard library only, and it closed the one failure mode that could actually cost me an account.
The ledger also protects the metadata step
The same discipline applies upstream. All three platforms reject a wrong CSV header instantly, before a human ever sees your work. Adobe's upload CSV has a 5-column header, Vecteezy wants 4 columns, Dreamstime wants 15. Get one column name wrong and the whole batch bounces.
make_metadata.py writes those exact headers, nothing else. It walks a folder of files, title-cases the filename as a first-pass title (replace it with real titles via --titles titles.json before you submit), and emits the platform-specific CSV:
python make_metadata.py ./my-assets --platform adobe --out metadata.csv --ext .jpg
Standard library only, like the tracker. The pair together means a re-run of the whole pipeline can never produce a double submission, because the ledger is checked before the CSV is built and again before anything leaves the machine.
Why not just use a database or a cloud tool
I considered it. Here is the honest math of my setup: the entire pipeline runs on a 1 vCPU, 2 GB RAM box with no GPU. That is the constraint I chose, because a small server costs almost nothing and a cloud account with a database is one more bill and one more thing to secure. A JSON or plain-text ledger reads and writes in milliseconds at any scale a solo seller hits. Even at thousands of assets, the file is a few hundred kilobytes.
The same constraint shaped the rest of the kit. Two third-party packages total: Pillow for product covers and reportlab for PDFs. Everything else is standard library. On a 2 GB box, dependencies are memory, and memory is the ceiling. I check peak RSS with resource.getrusage(RUSAGE_SELF).ru_maxrss, run one process at a time, and call gc.collect() between assets. A ledger with zero imports fits that budget perfectly.
The pattern generalizes: small state beats big memory
The deeper lesson is that automation fails not at the clever parts but at the boring parts. Making art is the fun part. What stalls people is metadata headers, duplicate uploads, and packaging. Those are all state problems. What have I already submitted? Which CSV does this platform need? What is already zipped? Each one is answerable with a tiny file and a few dozen lines of code.
The folder layout matters too. My project root has assets/, output/, packs/, research/, and scripts/, and the ledger lives alongside the outputs it tracks. One note I keep repeating to myself: never delete research/. I did once, to "clean up," and lost the notes behind several product ideas. The ledger and the research folder are the same idea: cheap, permanent, boring records that future-you will thank you for.
What this turned into
After packaging 39 finished products this way, about 856 MB of output from roughly 19,000 lines of pipeline code, backed by 300+ pages of research on the platform rules, I pulled the four scripts that did the real daily work into the pipeline starter kit: make_metadata.py for the Adobe, Vecteezy, and Dreamstime CSVs, upload_tracker.py for the ledger, md2pdf.py for turning Markdown into styled PDFs, and pack_product.py for zipping products with a 1280×720 cover and a 600×600 thumbnail. It runs on the same 1 vCPU / 2 GB server everything else runs on, and two of the four scripts need no install at all.
If you are building your own pipeline, the tracker alone is worth the ten minutes to rebuild from scratch. If you would rather skip the rebuild, it is in the kit with the rest.
Building digital products? Free guides & kits for digital product sellers.
Top comments (0)