Shipping a Seasonal AI Side Project on Cloudflare Workers: Lessons from a Pet Halloween Photo App
Most side-project advice assumes you have time: launch, iterate, grow slowly. A Halloween product gets about three weeks. If people can't find it, trust it and pay for it in that window, the next chance is a year away. This October I shipped HallowPaws. You upload a photo of your dog or cat, pick a costume, and get an HD Halloween portrait that still looks like your pet. Below are the engineering and product decisions that mattered for a short seasonal window. Most of them would apply to any AI photo side project. Stack: boring, cheap, scales to zero - TanStack Start + React 19, deployed to Cloudflare Workers - D1 for data, R2 for images, a Cloudflare Queue for generation jobs - better-auth for accounts, Stripe Checkout for one-time packs - An image-edit model behind an API aggregator, with a fallback model For a product that's busy for a few weeks and quiet the rest of the year, scale-to-zero pricing matters more than anything else on this list. Nothing sits idle and costs money in February. Decision 1: no prompt box, just presets The users are pet parents, not prompt engineers. So there is no free-text input anywhere. The only prompts that can run are 16 costume presets in one config file (vampire, witch, pumpkin, ghost, skeleton, pirate, wizard, mummy and so on). Each preset has just two creative fields, costume and scene , which get dropped into one shared template: Edit the reference photo: create a Halloween costume portrait of the pet from the reference photo. Keep the exact same animal identity: same species, breed, fur color and pattern, markings, eye color, ear shape, face proportions and expression, so the owner instantly recognizes their pet. Dress the pet in {costume}. The costume must fit the animal's anatomy naturally... Background: {scene}. Style: photorealistic professional pet photography, sharp focus on the eyes, detailed fur texture, shallow depth of field, vertical 4:5 portrait composition. Do not add any humans, people, hands, text, captions, logos or brand marks... The identity line comes before the costume line on purpose. For this kind of product, the quality bar isn't "nice AI art". It's whether a friend scrolling past would say "that's Max". Fixed presets also make results reproducible, so when a costume underperforms I fix one string in one file. Adding a costume or an SEO landing page means adding one entry, and the picker, sitemap and llms.txt all update from that config. Decision 2: watermark on the server, before storage The free tier is a low-res, watermarked preview. My first instinct was to ask the model to draw the watermark. Don't do that: the model treats it as a suggestion. Instead, previews get stamped inside the Worker with Photon (Rust compiled to WASM) before anything is written to R2. The image is downscaled to 768px wide and a tiled overlay is applied. The clean original never exists in storage, so there's no URL for anyone to guess. HD images skip this step completely. Two workerd gotchas: - The .wasm file has to be imported as a precompiled module (?module ), because compiling from bytes at runtime is blocked. - Load it lazily on first use, so a WASM problem can never break Worker startup for every other route. Decision 3: image calls are slower than your proxy Image-edit calls regularly take longer than Cloudflare's ~100-second proxy timeout. Run them inside the request and users see HTTP 524. So generation became a job: - POST /generate validates the photos (JPEG/PNG/WebP, up to 10 MB each), reserves credits, stores the photos in R2, inserts a job row, puts the job id on the queue, and returns right away. - The queue consumer runs the job. A queue invocation can run far longer than an HTTP request. - The client polls the job status every few seconds. Every failure path goes through one rule: mark the job failed and refund the reserved credits, exactly once. A sweeper fails and refunds anything stuck for more than 10 minutes. The provider wrapper also has a per-call timeout, so a hung primary model still leaves time to retry on the fallback model. Users never pay for a failed image, and that one rule removed a whole category of support emails. Decision 4: let people try before they sign up Asking for an account before showing any result kills a seasonal funnel. HallowPaws gives guests one free watermarked preview with no sign-up: - Guests are identified by an httpOnly cookie, rate-limited per hashed IP per rolling 24 hours, and capped globally per day to protect AI spend. - When the guest signs up, that preview is claimed into their new account as its free preview. - Signing up also gives one free HD credit, so a new user can unlock one clean, full-resolution portrait before paying anything. Decision 5: price for a spike, not a habit A monthly subscription for something people use for two weeks a year feels hostile. So HallowPaws sells one-time packs: - Treat: $1.99 for 4 HD images (classic costumes) - Spooky: $4.99 for 15 HD images (all 16 costumes) - Monster Party: $9.99 for 40 HD images, plus group portraits of up to 3 pets Pack definitions live server-side as the single source of truth. The client only sends a pack key. Checkout ignores any price or credit count in the request body and records the server's numbers on the order, and that order is what the webhook grants. Decision 6: honest demos Before/after examples are the most persuasive part of the landing page. They're also the easiest place to cut corners. The ones on HallowPaws come from royalty-free (CC0) pet photos, run through the same presets customers get, and the page says so. If your product is "it still looks like your pet", the demo can't use cherry-picked outputs from a different pipeline. What I'd tell someone building the next seasonal AI app - Ship the config, not the prompt box. Curated presets beat flexibility for consumer users and are far easier to debug. - Assume generation will outlive the request. Design for queues and polling from day one. - Treat credits like money from the start. Reserve first, refund on every failure path, and make the refund idempotent. - Enforce the free tier on the server. Watermarks and resolution limits belong in your code, not in the prompt. - Pick infrastructure that costs nothing in the off-season. If you have a dog or cat and five minutes, you can try a free preview at hallowpaws.com. I'm happy to answer questions about the Workers + Queue + Photon setup in the comments. Top comments (0)
Comments
No comments yet. Start the discussion.