DEV Community

The Locked Spec: Why Your Digital Products Look Inconsistent

The Locked Spec: Why Your Digital Products Look Inconsistent

The third rejection email arrived at 2 AM. Same platform, same reason: "CSV header malformed." I had already uploaded 24 files that week. Twenty-four. All of them manually re-typed into a spreadsheet because I was "saving time" by not automating metadata. That night I closed the laptop and counted the hours I had burned on headers, keywords, and filenames that a script should have handled. It was over 30 hours across two weeks. That was the moment I stopped treating packaging as an afterthought.

The problem is not the art

Making the art is the fun part. It is also not where products stall. They stall at four specific points: metadata, double-posting, turning a doc into something that looks like a product, and packaging. Every one of those is a rule problem, not a creativity problem.

Stock platforms enforce their specs without mercy. Adobe wants a 5-column CSV. Vecteezy wants 4 columns. Dreamstime wants 15. Get the header wrong by a single character and the whole batch is rejected instantly. No grace period. No partial import. The same is true of your product covers: a zip with a real cover sells, a bare file does not. That asymmetry is what makes a catalogue look inconsistent. Some products look finished. Some look like homework.

A locked spec beats good intentions

The fix is not discipline. Discipline fails on day 12. The fix is a spec that cannot drift.

The pipeline I ended up building writes the exact header each platform demands and nothing else. One script, make_metadata.py, takes a folder of files and produces metadata.csv for Adobe, vecteezy.csv for Vecteezy, and dreamstime.csv for Dreamstime. The tool does not negotiate. It emits the columns the platform asked for.

The second script, upload_tracker.py, solves the other silent killer: double-posting. Running your batch twice is the fastest way to get an account flagged. The tracker is a simple ledger. It remembers what you have already posted, so a re-run shows you what is pending instead of re-submitting everything.

python upload_tracker.py pending items.txt
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
Enter fullscreen mode Exit fullscreen mode

Three commands. That is the whole anti-double-post system.

Consistency is a rendering decision

The other half of looking inconsistent is visual. Your PDFs have mismatched fonts. Your covers have inconsistent dimensions. Your thumbnails are whatever the export dialog defaulted to that day.

Two more scripts close that gap. md2pdf.py turns a Markdown file into a clean, styled PDF with headings, tables, lists, and links. One command, one look, every time:

python md2pdf.py content.md product.pdf "My Product Title"
Enter fullscreen mode Exit fullscreen mode

pack_product.py bundles a product: it zips the files and renders a cover at 1280 by 720 and a thumbnail at 600 by 600. Same dimensions, same layout logic, every product:

python pack_product.py my-product "My Product" "A short subtitle" 9
Enter fullscreen mode Exit fullscreen mode

Out comes my-product.zip, my-product-cover.jpg, and my-product-thumb.jpg, written into a _system folder. Point it elsewhere with --dir or the PACK_ROOT environment variable. No editing the script.

The dependency list is deliberately tiny. Only two third party packages exist in the whole kit: Pillow for the covers, and reportlab for the PDF output. make_metadata.py and upload_tracker.py use the standard library alone and need no install at all.

The layout that keeps a catalogue from becoming a junk drawer

Tools alone do not fix inconsistency. Structure does. This is the layout the kit assumes, and the one that scales:

project/
  assets/            raw output (one file per asset)
  output/            ready-to-submit files + metadata.csv
  packs/             finished product zips + covers
  research/          your notes and sources
  scripts/           these tools
Enter fullscreen mode Exit fullscreen mode

Keep research/ forever. Your scrape logs and notes are the raw material for future products. I deleted mine once to "clean up." I will not repeat that mistake.

Proof it runs on almost nothing

This whole pipeline packaged 39 products, 856 MB of output, on a server with 1 vCPU, 2 GB of RAM, and no GPU. The parent project behind it is roughly 19,000 lines of pipeline code, backed by more than 300 pages of research that produced the rules the scripts enforce.

It works because of a few habits worth stealing even if you never touch this kit:

  • One process at a time. Never fan out image jobs in parallel on a 1-core box.
  • Free memory between items. Call gc.collect() after each asset.
  • Measure peak RSS, not average. resource.getrusage(RUSAGE_SELF).ru_maxrss gives you the real ceiling.
  • Process one file per subprocess for heavy render steps, so memory returns to the OS.
  • Render at the resolution you need. Inspecting a preview at 60 dpi beats rendering it at 300.

None of these are clever. They are just the difference between a pipeline that survives a 2 GB box and one that dies at file 40.

What actually fixes the inconsistency

Your products look inconsistent because the rules governing them live in your head, and your head is tired by product 30. Move the rules into scripts. Let the script refuse to emit a wrong header. Let the tracker refuse to double-post. Let the cover renderer refuse to produce a 900 by 400 image because you were in a hurry.

That is the whole argument of this kit. It is the four working scripts that turned one prompt engine into 39 finished products, not a tutorial that ends at "here is how you would do it." It is the same packaging layer that ran on 1 vCPU and 2 GB of RAM, and it plugs underneath a free stack: an orchestrator that schedules jobs and retries failures, and a local LLM proxy for model access.

If your catalogue looks like 39 different people made it, the fix is not more effort. It is a locked spec.

See the kit


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

Top comments (0)