I had a folder of 40 pages of good content and a customer waiting, and no product. This was the moment I realized the difference between "I have a document" and "I have something people will pay for" is mostly formatting. The content was done. The PDF looked like a printout from 2004. I almost shipped it that way.
Here is what I learned fixing that, and the command that made it a non-issue forever after.
A bare Markdown file is not a product
The first PDF I generated was default reportlab output: Times New Roman, no spacing logic, tables that overflowed the page. A buyer opens that and instantly assumes the rest of the product is as sloppy. Fair. The same content in a styled PDF reads as something worth money.
The fix was md2pdf.py, one of four scripts in my pipeline. It takes a Markdown file and renders headings, tables, lists, and links into a clean PDF using reportlab. One dependency, no framework, no cloud account.
python md2pdf.py content.md product.pdf "My Product Title"
That single command is what turned my docs folder into sellable add-ons. The styled PDF became a paid tier on top of assets I was already selling. Same content, one extra command, higher perceived value.
Why PDFs work as add-ons specifically
If you sell stock art, stickers, or templates, your buyers already have the files. What they often lack is the instructions, the usage guide, the color pairing notes, the print settings. A styled PDF answers that and justifies a price bump without any new art.
I sell through stock platforms too, and the rules there are strict in ways PDFs sidestep. Adobe Stock and Vecteezy reject uploads on metadata alone. Adobe's upload CSV has a 5-column header, Vecteezy's has 4, and a wrong header is an instant rejection before a human ever sees your work. make_metadata.py writes those exact headers. But those platforms take a cut and set the price. A direct PDF add-on is yours. You set the price, you keep the margin, and no review queue decides if it ships.
That is the whole appeal: one command turns content you already have into a product that does not answer to a platform reviewer.
The packaging step matters as much as the PDF
A styled PDF alone still looks unfinished in a store listing. The other half is a real cover and thumbnail. pack_product.py bundles the files into a zip and renders a 1280×720 cover and a 600×600 thumbnail. Pillow is the only image dependency.
python pack_product.py my-product "My Product" "A short subtitle" 9
It writes my-product.zip, the cover, and the thumb into a _system folder, redirectable with --dir or a PACK_ROOT environment variable. No editing the script. A listing with a real 1280×720 cover converts better than a bare zip, and I do not need a design tool open to make one.
It has to run on a tiny server
This pipeline runs on 1 vCPU, 2 GB RAM, no GPU. That constraint shaped every script. The habits that keep it alive:
- One process at a time. Never fan out render jobs in parallel on a 1-core box.
-
gc.collect()after each asset to free memory between items. - Measure peak RSS with
resource.getrusage(RUSAGE_SELF).ru_maxrss, not averages. Peak is what kills a 2 GB box. - One file per subprocess for heavy render steps, so memory returns to the OS.
- Render at the resolution you need. Inspecting a PDF preview at 60 dpi instead of 300 saves real memory.
reportlab is light compared to image generation, so PDF rendering is one of the few steps that never threatened the RAM ceiling. That makes it a good fit for a small server: cheap, fast, and predictable.
The versioning trap, and how I avoid double work
Once a PDF is a paid product, buyers expect updates. My mistake early on was regenerating everything by hand and losing track of which version shipped to whom. Two habits fixed it.
First, the Markdown source is the product. The PDF is an artifact. You never edit the PDF, you edit content.md and rerun the command. That means every update is one command and zero manual formatting.
Second, upload_tracker.py keeps a ledger of what has shipped. It was built for stock uploads, where running a batch twice is the fastest way to get an account flagged, but I use it for product versions too. pending shows what is not done, done marks an item, status gives the overview. A re-run never double-posts. When you are shipping updates across a catalogue of 39 products, that ledger is the difference between confidence and anxiety.
Where this fits in the bigger pipeline
These scripts are the packaging layer of a larger system. Around 19,000 lines of pipeline code, 300+ pages of research behind the rules, 39 finished products totaling 856 MB of output, all validated on that same 1 vCPU / 2 GB RAM server. The PDF command is the piece that turns written content, sticker usage guides, template instructions, prompt playbooks, into paid add-ons with no extra art required.
If you are building the same kind of pipeline and want the full system, including the AI sticker side of it, the workflow, the prompts, the packaging rules, I put all of it into one thing. It is the same playbook I use, not a summary of it.
Get ai sticker factory playbook
Building digital products? Free guides & kits for digital product sellers.
Top comments (0)