The live production platform behind GTIM's embroidery-digitizing business. Customers, digitizers and admins work in one system, digitizers bid on each job with stitch-count estimates, and payment, delivery and notifications are covered end to end.

Super Digitizing runs GTIM's embroidery-digitizing operation. Customers upload artwork and request quotes, a pool of digitizers estimate the work, and admins coordinate assignment, pricing, revisions and delivery. It replaced a legacy PHP/MySQL application and an email-heavy process with one platform where every role can see the current state of each order and the next action it owns.
The hardest part was the cutover, not the new features. The legacy system used a different data model, with integer keys and differently shaped orders, quotes, prices and payments. It also kept roughly 46,000 design files in loosely named folders with no reliable link back to their orders. All of that history had to land in the new PostgreSQL schema, attached to the right customer, without one customer's files ever reaching another.
I built a Next.js 16 client and a Laravel 13 REST API over PostgreSQL, with Redis-backed queues supervised by Horizon. Orders, quotes, edit requests and payments follow explicit lifecycles. Rules that cut across features, such as audit logging, per-role change tracking and overpayment refunds, are attached as model observers so that no future endpoint can bypass them. Everything ships as Docker services behind Nginx and Traefik, with Sentry on both tiers.
The platform is live at superdigitizing.com with migrated customer, order and file history. Prices come from competing digitizer estimates rather than guesswork. Customers receive production files only once their balance clears, and each role gets the emails and dashboard signals that concern it.
Role-aware dashboards for customers, digitizers and admins. The access token lives only in memory and is re-issued from an HTTP-only refresh cookie, and checkout uses the PayPal JS SDK.
Quote, order, edit-request and payment lifecycles with role and permission checks. Includes TOTP two-factor authentication, Cloudflare Turnstile and audited admin impersonation.
A UUID-keyed transactional schema with per-role change tracking. Redis backs the job queues and short-lived cached counts.
Queued email, payment reminders and maintenance commands. Email retries only on transient SMTP failures, so a retry can never send a duplicate message.
Artwork, machine files and previews are served only through expiring signed routes, and completed files unlock only when the balance due is zero.
Postgres, Redis, API, web and Nginx run as containers behind Traefik, with error monitoring on both client and server.
Each invited digitizer submits a stitch-count estimate, and the API derives the price from it: $20 covers up to 10,000 stitches, plus $1 for every additional 1,000. Bids therefore compare on the measure that actually drives cost. Admins select a digitizer from the competing estimates. A sole invitee is selected automatically, and invitations without a submission expire when the quote converts. A per-quote setting decides whether conversion assigns the winning digitizer directly or leaves the order for manual assignment.
Instead of running a WebSocket tier, the API keeps a separate change fingerprint for each audience on every order and quote. The changed_at timestamp moves only when something that role can actually see changes. Unread state is a single SQL comparison, so the badge count and list ordering agree on every page. Active views poll every 6 seconds and the global badge every 20 seconds. Polling pauses in background tabs and catches up when the tab regains focus.
Completed machine files download only once the balance due reaches zero. The API enforces this and serves the files through expiring signed URLs. When a price change creates an overpayment, a model observer issues the PayPal refund after the database transaction commits. A failed refund call therefore never rolls back a payment that was already captured, and no controller can forget to trigger the refund.
A validation rule rejects email addresses, phone numbers, URLs, social handles and messaging IDs in free-text fields between customers and digitizers. Communication stays inside auditable order threads instead of moving off-platform.
Users, orders, quotes, prices and payment splits moved through versioned SQL backfills and Python dump parsers. Legacy order numbering continues, so customers' existing references still work. A legacy:reconcile-files Artisan command then attaches files in six passes. It replays a previous mapping that had only partly executed (110 of 694 rows), matches filename prefixes, resolves customer uploads by exact basename and falls back to preview thumbnails. It runs from one in-memory index of the file tree and supports dry runs and per-ID targeting. Order and quote IDs overlap numerically, so it keeps the two strictly apart. Anything left unresolved goes into a report for manual follow-up.