I had 39 finished products sitting in a folder and a zip file I was proud of. Then I opened my sales page preview and saw a gray rectangle with a filename in it. Nobody buys a gray rectangle. That was the moment I learned the 1280×720 lesson: a zip file is not a product until it has a cover.
Here is what I actually did about it, and the rules I now bake into every package.
The cover is the product page
When you sell a digital download, the buyer never touches the zip before paying. All they see is the preview image your store renders. Gumroad, Etsy, and every stock platform work the same way: the thumbnail does the selling, the file just has to not break.
So the cover is not decoration. It is the entire sales surface. I settled on 1280×720 for covers and 600×600 for thumbnails because those two sizes render cleanly everywhere I sell, and they are cheap to generate. Pillow draws one in under a second on my 1 vCPU, 2 GB server. No GPU, no image API call, no cost per cover.
Before I scripted this, I was hand-making covers in an image editor. That was twenty minutes per product, which across 39 products was thirteen hours I did not have. The script does it in one command:
python pack_product.py my-product "My Product" "A short subtitle" 9
That writes the zip, the cover, and the thumbnail in one pass. Total third-party dependencies for the whole thing: Pillow. That is it.
Stock platforms reject wrong metadata instantly, and that is a different problem
The cover lesson applies to your own products. Stock platforms have a harsher rule: they reject the upload before a human ever looks at it if the CSV header is wrong. Adobe Stock wants a 5-column header. Vecteezy wants 4 columns. Dreamstime wants 15. Get one column wrong and the whole batch bounces.
I learned this by submitting a batch and watching all of it fail for a header mismatch. Not the art, not the keywords, the header. So make_metadata.py writes the exact header each platform demands and nothing else. No clever extras, no optional columns. The platforms do not reward creativity in metadata, they reward compliance.
One honest caveat the script also enforces: if you do not supply titles, it title-cases the filename and uses that. That is fine for a first pass and bad for submission. Replace it before you hit submit. Reviewers notice lazy titles.
Double-posting is the fastest way to get flagged
The second failure I had was running a batch twice. I lost track of what I had already uploaded, re-ran the queue, and posted duplicates. On stock platforms that is a flag on your account, not a shrug. So now there is a ledger:
python upload_tracker.py pending items.txt
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
It is a plain text ledger, standard library only, no database. A re-run checks the ledger first. Since I added it, zero double-posts across every batch. For context on scale: I have made about 1,700 assets total, 58 are live on Adobe Stock, and my first sale was $0.99. That first dollar proved the pipeline worked. The ledger is what keeps it working at batch size.
The 2 GB server rules that make any of this possible
None of this runs on a fancy machine. The whole pipeline runs on 1 vCPU and 2 GB RAM with no GPU. That forces habits worth adopting even if you have better hardware:
- One process at a time. Never fan out image jobs in parallel on a single core. It does not go faster, it goes OOM.
- Call
gc.collect()after each asset. Free memory between items or peak RSS climbs until the kill. - Measure peak RSS, not average:
resource.getrusage(RUSAGE_SELF).ru_maxrssgives the real ceiling. - Run heavy render steps one file per subprocess so memory returns to the OS.
- Render at the resolution you need. Inspecting a preview at 60 dpi instead of 300 is the difference between fitting in RAM and not.
Keep your research folder forever
One structural mistake I made once and will not repeat: I deleted my research folder to "clean up." Six weeks later I wanted the notes behind a product decision and they were gone. Scrapes, logs, notes, all of it is raw material for the next product. The folder layout I use now:
project/
assets/ raw output
output/ ready-to-submit files + metadata.csv
packs/ finished zips + covers
research/ never delete this
scripts/ the tools
Where this all lives now
Everything above is now four scripts: make_metadata.py for the Adobe, Vecteezy, and Dreamstime CSVs, upload_tracker.py for the ledger, md2pdf.py for turning Markdown docs into styled PDFs, and pack_product.py for the zip, the 1280×720 cover, and the 600×600 thumbnail. Two dependencies total, Pillow and reportlab, and two of the four scripts need nothing but the standard library. These are the exact scripts that packaged those 39 products, 856 MB of output, validated on that same tiny server. They are the packaging layer of the larger system I call the digital asset factory, which is where the 1,700 assets and the 58 live Adobe listings came from. If you want the tools instead of the story, it is here:
Building digital products? Free guides & kits for digital product sellers.
Top comments (0)