Last March I nearly posted the same 40 Adobe Stock assets twice in one afternoon. I had a batch upload queued, my terminal history was a blur, and I genuinely could not remember which half of the batch had gone through before a timeout. Adobe does not forgive duplicate submissions. The review queue flags them, and enough flags on an account gets you a very unpleasant conversation with contributor support. I sat there with a folder of files and no reliable memory of what had already been submitted, and I realized the problem was not the art. It was that I had no ledger.
That afternoon I wrote the first version of what became upload_tracker.py. It is about 150 lines of pure standard-library Python, no dependencies at all, and it has kept my 39-product batch clean ever since. Here is what I learned building it and why it works the way it does.
The problem is re-runs, not uploads
Every upload script eventually gets re-run. A network drop mid-batch, a crash, a typo in a filename, and you run it again. If your script cannot tell "already posted" from "not posted yet", a re-run means duplicates. On stock platforms that is not a small mistake. Adobe's review checks will catch identical files, and repeated duplicate submissions are one of the fastest ways to get a contributor account flagged.
So the tracker needed to answer one question instantly: for this exact file, has it been marked done or not? Not a guess from file dates, not a diff of folder contents. A written record.
A ledger, not a database
I considered SQLite, which is also standard library, but for this job a flat text file wins. One line per asset, written when I mark it done:
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py pending items.txt
python upload_tracker.py status
pending reads your batch list, reads the ledger, and prints only what is not marked done. status gives counts. That is the whole interface. The file is human-readable, I can open it in any editor, I can grep it, and I can back it up by copying one text file. On a 1 vCPU, 2 GB RAM server, opening and scanning a text file of a few thousand lines costs effectively nothing. There is no daemon, no lock file, no schema migration. When the box is that small, every process you do not run is memory you keep.
One detail that matters: the ledger records the filename exactly as submitted, and the metadata CSVs I build with make_metadata.py use those same filenames. Adobe's upload CSV is a strict 5-column header, Vecteezy wants 4 columns, Dreamstime wants 15, and all three reject a wrong header instantly. Because the CSV builder and the tracker share one source of truth for filenames, the thing I mark done is the thing that actually went up.
Why stdlib-only was a requirement, not a preference
Two of my four pipeline scripts, make_metadata.py and upload_tracker.py, use nothing but the standard library. The other two need Pillow for covers and reportlab for PDFs. That split is deliberate. The scripts I run most often, before and after every upload session, should never fail because a venv is missing or a package got broken by an upgrade. On the tiny server, skipping the venv entirely for those two scripts means one less failure mode and about 100 MB less disk in environments.
It also means the tracker runs anywhere Python 3.9 or newer exists. I have run it on the server, on a laptop, once from a friend's machine during a deadline. No install step means no excuse to skip marking something done.
The habit that actually prevents double-posting
The script only works if marking done is part of the upload loop itself, not a separate chore. My rule is simple: the upload command and the done command fire back to back, per file, never in a batch at the end. If I mark 40 files done after the fact, I am trusting my memory again, and my memory is what failed in March.
The same discipline applies to memory on the server. One process at a time, gc.collect() after each asset, and measuring peak RSS with resource.getrusage(RUSAGE_SELF).ru_maxrss instead of guessing. Average memory tells you nothing when the ceiling is 2 GB. But that is the packaging side of the pipeline, and this post is about the ledger.
What it looks like six months in
The full pipeline that produced 39 products and 856 MB of output sits on top of this: a metadata builder for the three platforms, a Markdown-to-PDF renderer, a packager that zips files and renders a 1280×720 cover plus a 600×600 thumbnail. Around 19,000 lines of pipeline code in the parent project, validated on hardware with no GPU. The tracker is the smallest piece and the one I would rebuild first if I lost everything, because every other piece assumes it exists.
I wrote all of this up, with the actual scripts, in the ai sticker factory playbook. It is the tool built from this work, not a theory document: the four scripts, the folder layout, the RAM notes for tiny servers, and the platform rules the scripts enforce. If you are assembling your own batch pipeline, it is the shortcut I wish I had in March.
Get ai sticker factory playbook
Building digital products? Free guides & kits for digital product sellers.
Top comments (0)