Dev.to AI 🤖 Ai 👁 0 📖 4 min read

Your Batch Job Posted Twice and Got the Account Flagged — Here's the Tracker That Prevents It**

I posted the same 212 stickers twice. Not on purpose. My batch script died halfway through the upload step, I fixed the error, and I re-ran the job from the top because the script had no idea what it had already sent. T

I posted the same 212 stickers twice.

Not on purpose. My batch script died halfway through the upload step, I fixed the error, and I re-ran the job from the top because the script had no idea what it had already sent. There was no tracker yet. There was a folder of files and a loop that assumed a clean slate every time.

Two days later the account was flagged, uploads were locked, and a support ticket asked me to explain the duplicates. Nothing malicious had happened. I had built a pipeline with no memory.

That is when I stopped treating the tracker as bookkeeping and started treating it as the safety rail for everything else.

The bug was state, not art

The art was fine. The metadata was fine. What broke was that my pipeline could not answer one question: what have I already submitted?

Every batch tool I had written assumed it was running for the first time. Re-run it and it reposts. Fix a bug mid-run and it reposts. Change one title and it reposts the other 211 files too, because nothing recorded which ones were already done.

The fix is not clever. It is a plain text ledger, one line per asset, plus three commands.

python upload_tracker.py pending items.txt      # what is NOT done yet
python upload_tracker.py done "asset-001.jpg"   # mark one as sent
python upload_tracker.py status                 # counts, totals

That is it. upload_tracker.py uses the standard library alone. No sqlite, no database, no service. For a catalogue in the low thousands, a text file is the correct data store and I skipped the schema for a reason.

The rule I run now: pending before every batch, never after. If pending does not match what I expect to send, the batch does not start. That single check would have saved me the flagged account.

One honest limit: the tracker prevents double-posting from my side. It does not fix a platform that already has your file. It is a local guard, not a dedupe service.

Headers are the other place batches die

Duplicate submissions get you flagged. Bad CSV headers get you rejected at ingest, before a human ever sees the work.

Three channels, three different headers. Adobe takes a 5 column metadata CSV. Vecteezy takes 4 columns. Dreamstime takes 15. Get one column wrong and the whole import fails, usually with a message that tells you nothing useful.

make_metadata.py writes exactly the header each channel demands and nothing else:

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

It also takes --titles titles.json if you have real titles and keywords. Without that flag it title-cases the filename and uses it as the title. That is a first pass, not a submission. Replace it before you send anything.

What a 1 vCPU box taught me

The whole pipeline ran on 1 vCPU, 2 GB RAM, no GPU. That constraint is not a limitation to work around later, it is the reason the code stays simple. Five habits got me through 1,860 assets without an out of memory kill:

One process at a time. Never fan out image jobs in parallel on a single core, you just trade CPU time for swap.

Free memory between items. gc.collect() after each asset, not once at the end.

Measure peak, not average. resource.getrusage(RUSAGE_SELF).ru_maxrss gives the real ceiling. Average RSS will tell you everything is fine right up until the process dies.

One file per subprocess for heavy renders, so memory goes back to the OS instead of sitting in a pool.

Render at the size you need. Inspecting a preview at 60 dpi beats rendering it at 300 and waiting.

Packaging is part of the batch, not an afterthought

A zip named product.zip with no image sells nothing. The batch output needs a cover at 1280 by 720 and a thumbnail at 600 by 600, generated in the same run as the zip, or it never gets made.

pack_product.py does all three in one command and writes into a _system folder next to wherever you run it. Point it elsewhere with --dir or the PACK_ROOT environment variable. Only third party dependency in that script is Pillow.

The folder layout that survived 39 packaged products:

project/
  assets/     raw output
  output/     ready to submit files + metadata.csv
  packs/      finished zips, covers, thumbs
  research/   notes and sources
  scripts/    the tools

Keep research/. I deleted mine once to clean up and spent a week rebuilding sources I had already read.

The numbers, plainly

1,860 assets made. 1,077 ready or submitted. 39 products packaged, 856 MB of output. Roughly 19,000 lines of pipeline code in the parent project, backed by 300 or more pages of research on the rules these scripts enforce. All of it validated on 1 vCPU, 2 GB RAM, no GPU.

First paid sale: Digital Asset Factory, $2.80.

$2.80 is not a milestone, it is a calibration. It confirmed the checkout works and the delivery works. The rest is volume, and volume only happens if the batch can be re-run safely, which is where this post started.

Where the tracker lives

The four scripts are the packaging and safety layer. make_metadata.py and upload_tracker.py need nothing but Python. md2pdf.py and pack_product.py pull in reportlab and Pillow, and that is the entire dependency list.

If your only problem right now is double-posting, the tracker alone will fix it and you do not need anything else from me.

The full Digital Asset Factory is the wider system these scripts sit inside: the orchestration, the research notes, the metadata rules for all three channels, and the packaging layer. I built it after the flagged account, so nobody else has to learn that lesson the way I did.

Get digital asset factory

Building digital products? Free guides & kits for digital product sellers.

📰 Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.