I put a zip on Gumroad and walked away. Nothing happened. No sales, no clicks worth reading. The files inside were fine. The cover was a screenshot I took in a hurry, 900 pixels wide, squashed. I opened the listing a week later and understood the problem in about two seconds.
The cover is not decoration. For digital assets it is the product.
That is the 1280Ã720 lesson.
A zip without a cover is not shippable
Buyers never open your zip first. They see the cover, then the thumbnail, then maybe the contents. If the cover looks like a raw file dump, they scroll past. The zip could hold the best stickers on the platform and it does not matter, because nobody gets that far.
I stopped treating covers as an afterthought. Every product now ships with two images, made by the same script that builds the zip:
- Cover: 1280Ã720. This is the size listed in the product image rules and it renders clean on a store page.
- Thumbnail: 600Ã600. Square, for grids and search results.
Both come out of pack_product.py. One command:
python pack_product.py my-product "My Product" "A short subtitle" 9
That writes my-product.zip, my-product-cover.jpg, and my-product-thumb.jpg into a _system folder next to where I run it. It uses Pillow, one dependency, nothing else. Point the output somewhere else with --dir or the PACK_ROOT environment variable, no need to edit the script.
The lesson is boring and it cost me a product cycle: package the cover at the same moment you package the files. Never let them be two separate jobs.
The metadata headers are just as unforgiving
Same story on the stock side. I had 1,700 assets made. I put 58 live on Adobe. First sale was $0.99. That number is small and real, and the reason so few went live was not the art. It was the CSV headers.
Adobe wants a 5-column header. Vecteezy wants 4. Dreamstime wants 15. Get the header wrong by one column and the upload is rejected instantly. No partial import, no helpful error, just a no.
make_metadata.py exists for that reason. It writes the exact 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
python make_metadata.py ./my-assets --platform dreamstime --out dreamstime.csv --ext .jpg
Pass --titles titles.json to supply real titles and keywords. Without it, the filename is title-cased and used as the title. That is fine for a first pass. Replace it before you submit, because a filename is not a title.
The other thing that gets accounts flagged is running the batch twice. upload_tracker.py is a ledger that remembers what you already posted:
python upload_tracker.py pending items.txt
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
Double-posting is the fastest way to lose a channel. The tracker makes it impossible.
One core, two gigabytes, no GPU
All of this ran on 1 vCPU and 2 GB RAM. No GPU. The habits that make that work are the same ones that keep a cover render from killing the box:
- One process at a time. Never fan out image jobs in parallel on a 1-core machine.
- Call
gc.collect()after each asset. Memory does not come back on its own. - Measure peak RSS, not average.
resource.getrusage(RUSAGE_SELF).ru_maxrssshows the real ceiling. - Render at the resolution you need. Inspecting a preview at 60 dpi beats rendering at 300.
The full pipeline packaged 39 products, 856 MB of output, behind roughly 19,000 lines of code and 300+ pages of research. None of that needed a bigger server. It needed fewer parallel jobs and honest memory numbers.
What I would tell you to do first
Do not start with 1,700 assets. Start with one product, one zip, one cover at 1280Ã720, one thumbnail at 600Ã600. Confirm the image shows up the way you want on the store page. Then do twenty. The pipeline only helps after the packaging is right, and the packaging is the part people skip.
The four scripts that do this work are make_metadata.py, upload_tracker.py, md2pdf.py, and pack_product.py. Two of them use only the Python standard library. The other two need Pillow and reportlab. No framework, no cloud account.
I built the full system from this work. It is the same pipeline, with the orchestrator, the tracking, and the packaging in one place.
Building digital products? Free guides & kits for digital product sellers.
Top comments (0)