DEV Community

Cover image for Why Venv Is Your First Customer Touchpoint (And Why It Almost Always Fails)**

Why Venv Is Your First Customer Touchpoint (And Why It Almost Always Fails)**

The first time someone bought a script bundle from me, they never got to the scripts. They emailed me twenty minutes later: "pip won't install reportlab, it says no matching distribution." They were on Python 3.6. The install instructions said Python 3.9+, but they had skimmed, everyone skims. That was the moment I understood something I had completely backwards: for a solo seller of digital products, the venv setup is not plumbing. It is the first customer touchpoint, and it is where most sales quietly die.

I run a small automated pipeline that makes and sells digital assets. 1,700 assets made so far, 58 live on Adobe Stock, first sale $0.99. The pipeline runs on a 1 vCPU, 2 GB RAM server with no GPU. The scripts are boring on purpose: make_metadata.py builds the Adobe 5-column, Vecteezy 4-column, and Dreamstime 15-column upload CSVs, upload_tracker.py keeps me from double-posting, md2pdf.py renders Markdown into a styled PDF, and pack_product.py zips a product and renders a 1280x720 cover plus a 600x600 thumbnail. Two third-party packages total: Pillow and reportlab. The other two scripts use only the standard library.

Here is what I learned about setup, the hard way.

Nobody reads your install section, so make it skippable

The buyer who emailed me had already paid. They were motivated, and they still failed at step one. That is the baseline. Most will fail harder. So I changed the structure: the two scripts that need nothing but Python are usable before any install at all. make_metadata.py and upload_tracker.py import nothing outside the standard library. A buyer can generate a correct Adobe CSV in the first ninety seconds, before they ever see the word venv. That is the real trick: give them a win before you give them a dependency.

Only then does the venv appear, for the two scripts that need Pillow and reportlab. And I write it out fully, including the Windows activation path, because half my buyers are on Windows and source .venv/bin/activate is a dead end for them. .venv\Scripts\activate or nothing.

The header is the actual product

The other touchpoint people underestimate is the CSV header. All three stock platforms reject a wrong header instantly, and the rejection tells you almost nothing. Adobe expects its 5-column format. Vecteezy expects 4 columns. Dreamstime wants 15. If your script writes a 6th column, or names one field slightly wrong, the upload fails and the buyer assumes your tool is broken, not their CSV. So make_metadata.py writes exactly the header each platform demands and nothing else. No extra convenience columns. This is not a style choice, it is the difference between "works" and "refund request".

Same with double-posting. The fastest way to get a stock account flagged is running your batch twice. upload_tracker.py is a plain ledger: pending shows what is not done, done asset-001.jpg marks one item, status prints the summary. Re-runs are safe. When a buyer re-runs something twice because they were not sure it worked the first time, and it is, the ledger is what saves their account.

Constraints force good setup design

My server has 2 GB RAM and one core. That constraint shaped everything, including the install story. Two dependencies is not minimalism for its own sake. Every package you add is another version pin, another wheel that might not exist for the buyer's Python, another support email. Pillow and reportlab are the two I actually need, so they are the only two in requirements.txt.

The same logic applies at runtime. One process at a time, never fan out image jobs in parallel on a 1-core box. gc.collect() after each asset. Measure peak RSS with resource.getrusage(RUSAGE_SELF).ru_maxrss, not averages, because the peak is what OOM-kills you. Heavy render steps go one file per subprocess so memory returns to the OS. Inspect previews at 60 dpi instead of rendering at 300. All of this came from watching a 2 GB box fall over, and all of it is in the scripts, not in a README the buyer will not read.

Write the docs for the person who fails first

I now test every setup section the same way: fresh machine, wrong Python version installed, skim-read my own instructions. If I can fail, a buyer can. Three fixes that came out of that: state the Python version on the first line of install, not in a code comment. Show the Windows command inline next to the Unix one. And make the first runnable command produce something visible, a real CSV on disk, in under a minute.

Folder structure is part of this too. The layout the tools assume is fixed: assets/ for raw output, output/ for ready-to-submit files plus the CSV, packs/ for finished zips and covers, research/ for notes. I once deleted my research/ folder to "clean up" and lost the raw material for future products. Never again. The scripts point at that structure, so a buyer who follows it gets a working pipeline instead of a junk drawer.

The honest part

This is not a framework and there is no dashboard. It is four standalone Python scripts, about as unglamorous as tooling gets, and they are the same ones that packaged 39 products, 856 MB of output, from roughly 19,000 lines of pipeline code backed by 300+ pages of research. They were validated on that same 1 vCPU, 2 GB RAM box. The numbers are small and real: 58 assets live on Adobe, first sale $0.99. I am not selling a lifestyle, I am selling the packaging layer I use every week.

If you are building your own asset pipeline and the boring parts, metadata headers, double-post protection, PDFs, covers, zips, are the part slowing you down, I put those exact scripts together as the Pipeline Starter Kit. It sits inside the larger system I call digital asset factory, which is the full pipeline built from this work.

Get digital asset factory


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

Top comments (0)