Inspiration

Digital messaging is instant, convenient, and utterly devoid of warmth. When we want to send a meaningful note—whether for a birthday, thank-you, or holiday—a text message or generic e-card often feels impersonal. Physical cards offer that personal touch, but they require physical materials, trips to the post office, and days in transit.

We created Penmanship to bridge that gap. We wanted to make sending a digital card feel as intimate and thoughtful as receiving a physical one—preserving the personal beauty of authentic handwriting while leveraging the speed and reach of the web.

What it does

Penmanship is a web application that lets users generate and share personalized 3D greeting cards and postcards rendered in authentic human handwriting styles.

Users sign in with Google to manage a library of saved handwriting samples or choose from built-in styles. After describing their desired card design and typing a message, Penmanship generates two high-resolution images: a custom front design face and a writing surface containing the message in their chosen handwriting style. Users can inspect their creations in an interactive 3D card viewer—flipping postcards or opening greeting cards—and share read-only views directly to recipients via private links.

Some cards created with Penmanship (public share URL):

How we built it

Penmanship was built using a modern full-stack monorepo architecture:

  • Frontend: Built with Next.js 16, React 19, Tailwind CSS, and shadcn/ui. The 3D viewer is powered by @react-three/fiber and @react-three/drei, rendering interactive WebGL textures for card faces.

  • Backend: Built using FastAPI and Pydantic v2. It handles model routing, authenticates requests via NextAuth.js JWT tokens, streams progress back to the client using Server-Sent Events (SSE), and serves CORS-compliant texture proxies for WebGL.

  • Image Generation & Storage: Powered by GMICloud (gpt-image-2-generate for prompt-based styles and designs, and gpt-image-2-edit for custom reference handwriting samples). Backblaze B2 serves as the single source of truth for all persistent storage—storing JSON metadata, user profile data, and image assets without relying on a traditional database.

Challenges we ran into

  • Cryptic GMICloud API Errors: The underlying GMICloud API returns generic error messages for policy blocks, such as returning "Generation rejected; please review the prompt and parameters then retry" when a prompt triggers copyright restrictions (e.g., requesting a "painting of the avengers"). Surfacing helpful feedback to users required handling these non-specific API rejections gracefully without confusing the user.

  • WebGL Texture Loading & CORS: WebGL (useTexture) enforces strict cross-origin checks. Presigned B2 URLs lacked custom CORS headers, causing canvas tainting errors when loading textures directly in Three.js. We solved this by proxying texture streams directly through our FastAPI server (which attaches CORS headers) and using blob object URLs on the client.

  • Zero-Database Architecture: Relying solely on Backblaze B2 for state management meant designing strict object path conventions, managing meta.json lifecycles (handling transitions from pending to complete/failed), and maintaining index arrays manually with atomic read-write patterns.

  • Model Routing Invariants: Ensuring that reference image editing (gpt-image-2-edit) and prompt generation (gpt-image-2-generate) were strictly isolated based on whether a user chose a default or saved style required robust backend validation.

Accomplishments that we're proud of

  • Smooth SSE Integration: Built an end-to-end Server-Sent Events pipeline that provides live feedback to the user as their card's design and handwriting faces are rendered and stored.

  • Interactive 3D Viewer: Crafted custom 3D models for both postcards (flip interaction) and greeting cards (realistic open/close book fold) that make viewing digital cards feel tactile and real.

  • Clean Architectural Layering: Implemented rigid structural invariants (such as restricting genblaze_* functions strictly to pipelines and keeping storage backends isolated) verified automatically via custom validation scripts.

What we learned

  • Client-Side WebGL Constraints: Navigating canvas security rules and memory management when dynamically switching high-resolution textures in @react-three/fiber.

  • Optimizing AI Pipeline Latency: Overlapping generation tasks (such as pre-generating design previews early while the user types their message) drastically improves perceived application performance.

  • Lightweight Architecture: Building a production-ready application without a traditional SQL/NoSQL database requires careful design around event loops and asset indexing, but offers immense simplicity in deployment.

What's next for penmanship.me

  • Corporate Bulk Sending: Allowing teams and companies to generate personalized cards in bulk—using templates (a single custom design and base message), while dynamically addressing each card to individual recipients and rendering unique handwriting details

  • Physical Print & Ship Integration: Partnering with print-on-demand APIs to allow users to mail real, physical postcards straight from their digital creations.

  • Custom Font & Vector Tracing: Converting user handwriting samples into full TTF/OTF font files for even faster offline rendering.

  • Audio & Video Notes: Allowing creators to attach voice notes or quick video messages inside the 3D card viewer alongside their handwritten text.

  • Collaborative Cards: Enabling multiple friends to sign the inside panel of a single greeting card before sending it to a recipient.

Built With

Share this project:

Updates