Journal entry

Shipping ulogo.it From Prototype to Production

ulogo.it product workspace

Shipping ulogo.it From Prototype to Production

ulogo.it is now live as a focused logo cleanup and generation bench. The product is deliberately narrow: upload a rough logo or generate a first draft, then get back a practical web-ready pack instead of a folder full of random exports.

The final shape is simple on the surface. The first screen is the tool, not a marketing page. You choose between upload and generate, drop in a file or write a prompt, run the job, then review a before/after pack with the assets that matter for shipping: clean PNG, SVG where vectorization is good enough, mark, favicon set, Open Graph preview, install snippets, and a ZIP.

The work underneath is where the project became interesting.

Product surface

The main constraint was speed to usefulness. Most logo tools either stop at background removal or drift into a heavy brand-studio experience. I wanted the middle: a small workshop that can take a messy source and return something a developer or founder can use on a real site.

That shaped the interface:

  • anonymous visitors can try 2 cleanups and 1 generated draft
  • signed-in users get a starter balance of 10 credits
  • cleanup costs 1 credit and generation costs 4 credits
  • prepaid credit packs are one-off purchases, not subscriptions
  • project history is saved for signed-in users
  • ready packs expose downloads, previews, and copyable install snippets

The app uses Google OAuth only. That kept the account model tighter: no local password flow, no email verification system, no account-recovery surface to maintain. If a user signs in, Google owns identity recovery and MFA, while ulogo.it owns sessions, credits, projects, and assets.

The processing pipeline

The core domain ended up as a small set of durable objects: source acquisitions, projects, processing jobs, assets, billing events, API keys, and webhooks. The web app creates the project and job; the worker owns the expensive image work.

For uploads, the app validates the input, applies abuse checks, stores the source, and queues processing through BullMQ on Valkey. The worker then normalizes the image, removes rough background friction, crops and recenters the result, renders the logo and mark outputs, builds favicon assets, generates an Open Graph image, and packages the final ZIP.

For generated drafts, the app runs an image generation lane first, then feeds the selected source into the same cleanup pipeline. The production config supports Gemini image generation and OpenAI gpt-image-2, with the route chosen by entitlement and runtime settings. Vector output uses provider-backed vectorization when available, with local fallback for simple marks and explicit degraded-pack handling when SVG quality is not good enough.

The important production detail is that a failed optional step should not always fail the whole job. If vectorization is unavailable, the pack can still be useful as PNG and favicon outputs. If a generated draft fails, credits need to be handled correctly. If a queue job gets stuck, the admin surface needs enough state to retry, clear, or refund with an audit trail.

Credits, billing, and abuse controls

The product uses credits because the expensive actions are discrete. Users do not need a subscription to clean up a logo once. The live pricing ladder is prepaid: 30, 60, 160, and 400 credits, with Polar handling checkout and webhooks.

Abuse protection sits in layers:

  • Cloudflare handles DNS, public TLS, WAF, and coarse rate limits
  • Turnstile protects expensive browser flows
  • Next.js API routes enforce Valkey-backed rate limits before accepting heavy work
  • Postgres keeps durable credit, quota, and project state
  • the worker consumes credits, records events, and refunds where the failure mode requires it

That layered model matters because image generation and file processing are expensive enough that a friendly UI alone is not a security model.

Developer API and CLI

The browser app is only one surface. ulogo.it also has a developer API under /api/v1, a Scalar API reference, and a separate npm CLI: ulogo-cli.

The API includes project cleanup, generation, project status, asset listing, asset download, eligible edit revisions, batch manifests, and webhooks. API keys are created from the account surface, support scopes and optional IP allowlists, and job creation supports idempotency keys. Errors return application/problem+json with request IDs and rate-limit headers.

That was worth building because logo cleanup is often a build step. A CLI can sit in a repo, a launch script, or an automation flow in a way a browser-only app cannot.

Production lane

The production path is as important as the feature work. The app runs as a Next.js web service plus a worker service. GitHub Actions builds GHCR images. Dokploy pulls those images and runs the compose stack. Postgres and Valkey are managed separately in Dokploy. Assets, generated drafts, processed outputs, and ZIP packs live in Cloudflare R2.

The bootstrap script manages the repetitive production work: Dokploy project setup, compose app settings, runtime env, domains, Cloudflare DNS, Turnstile, rate-limit rules, R2, Polar products, and webhook configuration. Database migrations run as a one-shot service with an advisory lock before the app and worker start.

Analytics are intentionally consent-gated. GA4 and PostHog only run after the optional analytics choice, which keeps the launch instrumentation useful without turning the first visit into a tracking wall.

What changed by shipping it

The early version was mostly a stack: Next.js, a worker, Postgres, Valkey, image tooling, storage, and deployment scripts. The shipped version is a product:

  • it has a clear first-run path
  • it has anonymous trial behavior
  • it has real credits and billing
  • it has durable job state and admin recovery tools
  • it has downloadable outputs that match the actual use case
  • it has a documented API and CLI path
  • it has production DNS, storage, queues, migrations, analytics, and abuse controls

That is the difference between a demo and an app I can keep online.

The current result is live at ulogo.it.