Queue-driven fulfillment with nightly batching
Ship a Postcard
Design, pay for and mail real postcards: AI artwork, a developer API and a nightly batch engine that hands orders to a commercial print network.
- Laravel 13
- React 18
- Inertia.js 2 (SSR)
- MySQL
- Stripe
- AWS S3
- Lob
- OpenAI
Overview
Ship a Postcard is a full-stack web application for designing, paying for and mailing physical postcards. It began as a personal project exploring where generative AI meets physical logistics, and it runs in production at shipapostcard.com. Senders can be anywhere in the world; recipients currently need a US address, including Alaska, Hawaii and US territories.
The stack is Laravel 13 with queues and the scheduler, and a React 18 front end served through Inertia.js 2 with server-side rendering. Data lives in MySQL and files in AWS S3 behind a CDN. Stripe handles payments, and Lob’s commercial print network handles printing and USPS delivery.
From pixels to paper
Users either upload a photo or generate artwork from a prompt with GPT-Image-2, crop it in the browser, write a message and check out. Guests can design a card first and sign up only when they’re ready to pay. The server then renders print-ready output at 300 DPI.
Image generation costs AI credits, sold in packs through Stripe. Uploading your own photo is free. Prompts and messages pass through the OpenAI Moderation API.
Every card carries a QR code. Recipients can scan it to reply to the sender or download the artwork, without creating an account.
Built for businesses as well
- Workspaces. Multi-tenant teams with invitations. Orders, campaigns, recipients, API keys and webhooks all belong to a workspace.
- Bulk campaigns. Upload recipients as CSV. Every address goes through USPS verification, and volume pricing applies automatically.
- Developer API. A versioned REST API (
/api/v1) with bearer tokens, idempotent checkout so a retried request can’t create a second order, request logging, and outgoing webhooks for fulfillment events.
The nightly batch engine
API orders are not sent to the printer one by one. They queue during the day, and at 23:59 a scheduled job prices the day’s orders by volume, charges each workspace’s saved card and dispatches them for printing.
After dispatch, Lob webhooks track each card through to delivery, and a nightly job computes transit metrics. The print provider sits behind configuration (POSTCARD_PROVIDER), so fulfillment can switch to a backup provider without code changes.
Findable by search engines and AI assistants
- Every public page renders on the server with a canonical URL,
hreflangalternates, Open Graph tags and schema.org JSON-LD: Organization, WebSite, Product with live prices, FAQPage and BlogPosting. - Each of the four languages (English, Spanish, Italian, Portuguese) has its own crawlable URL.
sitemap.xmlandllms.txtare generated from the routes, published blog posts and current pricing, so they never go stale.- Private pages send
X-Robots-Tag: noindex, and error pages return their real HTTP status.
Operations
An admin panel covers orders, users, support tickets, promo codes, expenses, blocklists, blog and legal pages, backups and logs. Postmark handles transactional and inbound email, and orders can also be ingested from Amazon.
Every push to main deploys through a script that puts the app in maintenance mode, resets to the new commit, installs dependencies, builds the client and SSR bundles, runs migrations, rebuilds caches, restarts the queue workers and the SSR process, and brings the app back up. The test suite runs on in-memory SQLite and never calls external services.
Key decisions
- Batch API orders instead of printing each one immediately. The daily batch lets each run price orders by volume and charge them in one pass. The cost is that API orders wait until the nightly run rather than going out instantly.
- Make retries safe. Idempotent checkout means a client can retry a request after a network error without creating a duplicate order or charge.
- Keep the print provider swappable. The integration hides behind a configurable provider, so fulfillment isn’t tied to a single vendor.
- Generate discovery files from the source of truth. Building the sitemap and
llms.txtfrom routes, posts and live pricing removes a whole category of stale SEO metadata.
Try it
Ship a Postcard is live at shipapostcard.com.