E-Cards Platform — Template Engine + LLM Batch Personalization
A digital card system that preserves an InDesign designer's behaviors, then batch-generates personalized cards with LLM-parsed names
The company had been designing staff e-cards in Adobe InDesign for years, driven by a JSX script that named every text frame ('Nombre', 'Cargo', 'In', 'Phone', 'Web') and auto-fit type to the card. It worked — and the designer who understood it was the bottleneck.
I rebuilt it as a web platform: a Fabric.js template designer that reproduces the InDesign behaviors down to per-word brand coloring, a Python parser that ingests messy contact spreadsheets and uses an LLM to clean Spanish names, and a BullMQ render worker that batch-generates thousands of cards into S3-compatible storage.
The Problem
Manual card design in InDesign was too slow for batch work, and the JSX pipeline that automated it depended on one person. The team needed to design a template once, then personalize it across an entire contact list — names parsed correctly, per-word brand coloring preserved, auto-fit respected, and rendered at 144 DPI.
Two harder sub-problems hid inside that:
1. Contact lists arrived messy — first name only, "surname first" Spanish formatting, 8-digit local phone numbers needing E.164 normalization, ext. vs. mobile ambiguity. Manual cleanup didn't scale. 2. Every existing tool failed at behavioral parity with the designer's InDesign habits (named objects, dynamic "In" icon clamp positioning, per-word CodeBlue-first coloring, auto-fit minimum 4pt, 0.1 decrement). Switching tools meant retraining the designer — a non-starter.
The Approach
A Next.js 16 front end with a Fabric.js canvas editor reproducing the InDesign behaviors ('GenerateCardsBvars.jsx' semantics: named elements, multi-color text, dynamic icon clamp, auto-fit, 144 DPI export). Drag-and-drop batch upload (.csv/.txt/.vcf/.xls/.xlsx, 10MB cap).
A Python batch-parsing service that handles the messy contact problem: 40+ field aliases ('email', 'correo', 'e-mail' → canonical), Spanish name parsing with LLM assistance (OpenAI GPT-4o-mini primary, Anthropic Claude 3.5 Sonnet fallback, DeepSeek cost-effective fallback), credit-metered per call, with an "as-is" fallback when credits run out. Phone-vs-extension detection for Costa Rican 8-digit numbers → E.164.
Hybrid storage on purpose: 5 searchable fields in PostgreSQL, the full 35+ vCard field set in Cassandra (event log + canonical record). A BullMQ render worker renders batches in parallel (concurrency 4, max 3 retries, 10 jobs/sec) into SeaweedFS via the AWS SDK v3 with multipart upload. Output presigned for download.
OAuth 2.0 with PKCE (S256) against the sibling Tools Dashboard identity provider — the e-cards app does not own its own auth, by design. All cookie-based, RS256-signed JWT, SameSite=Lax. CI: a 5-job GitHub Actions pipeline (compose-sanity, api-server, render-worker, front-cards, shared-types) gating every push.
The Outcome
A production platform that parses ~1,000 contact records in under 30 seconds (sub-100ms on indexed field search) and renders thousands of cards per batch with the same visual behaviors the InDesign designer relied on — no retraining needed.
The InDesign pipeline was retired. The LLM name-parsing fallback ("as-is" when credits run out) meant a billing lapse never broke the batch — a small design decision that paid for itself the first time it happened.
Key Takeaway
Behavioral parity is the part of porting a legacy tool that everyone underestimates. Reproducing the named InDesign objects and the per-word brand coloring — not the rendering — was what let the designer actually switch.
Ready to Build Your Platform?
One 30-minute call to see if we're a fit. No pitch. No pressure. Just a conversation about what you need to build.
$20K–$25K for an MVP. $30K–$80K for a full platform. Fixed price, milestone-gated. 50% upfront.