Analytical Ants built a virtual business card system in an afternoon: a QR code, a custom card page, and a vCard download that saves name, title, phone, email, and profile photo directly to any phone.
By William Rodriguez | Published: May 2026
TL;DR
Analytical Ants is a data analytics and business application consultancy that builds reporting platforms, data systems, and operational applications for mid-market companies.
At a recent networking event, the question came up: do you have a card? No paper card. No digital card. Just an email address recited out loud and hoped to be remembered.
So we looked at the subscription options, ran the numbers, and built something instead.
Subscription virtual card services work, but they introduce two risks that compound over time.
The first is cost at scale. At $12 per person per month, 10 employees costs $1,440 per year. Fifty employees: $7,200. One hundred: $14,400. That is not infrastructure spend. It is not security, analytics, or compliance. It is a recurring fee to host a page that displays contact information your platform already owns.
The second is dependency. Cancel the subscription and every QR code you have ever distributed goes dead simultaneously. Every printed card, every email signature, every desk display, every conference badge points to a URL that no longer resolves. The sunk cost of distributing those QR codes becomes visible the moment you try to leave.
Neither risk is acceptable when the alternative is an afternoon of work.
The economics get clearer as the team grows.
The marginal cost of adding the 101st employee to a custom-built system is the time it takes to type a config entry and generate a QR code. On a subscription platform, it is another $12 per month, forever.
There is also a durability argument. The virtual card pages Analytical Ants built today will resolve as long as the website runs, regardless of what happens to any third-party vendor.
The system has three components:
noindex, so it does not appear in search engine results. The page is reachable only by people who were handed the QR code or a direct link..vcf file contains name, title, company, phone, email, LinkedIn URL, website, and a profile photo embedded directly in the file as base64-encoded JPEG. When someone taps "Save Contact," their phone's native contacts app handles the import. No third-party app required.
Rendered on iPhone 12 emulation — dark mode
Speed of execution comes from architecture decisions made before this feature existed.
The card page is a single dynamic route in Next.js: app/card/[slug]/page.tsx. All team members are defined in one typed config object in lib/cards.ts. Next.js reads that config at build time and pre-renders every card as a static page. There is no database, no API, no runtime cost per visit.
This is the same principle behind the contact form Analytical Ants built earlier this year: one React component, one config point, two brands served from the same codebase. And the visitor identification layer added without touching the core application. Each investment in a clean architecture lowers the cost of the next requirement.
When the platform is built to absorb change, new features do not require new projects.
The build decision here was straightforward because the existing platform could absorb it cleanly. No new infrastructure, no new vendors, no meaningful added complexity. The requirement fit the architecture.
That is not always the case, and buying is often the right answer. But when a subscription tool does something a team's existing stack can handle in an afternoon, it is worth running the numbers before committing to a recurring per-seat fee that scales with headcount.
For organizations already carrying multiple subscription tools, that review tends to surface more savings than expected. If it is a conversation worth having, the Analytical Ants team is a good place to start.
What is a virtual business card? A virtual business card is a web page that displays contact information and allows the viewer to save it directly to their phone. The page is typically reached by scanning a QR code. Unlike a paper card, it can include a profile photo, clickable links, and a one-tap download that creates a contact record in the phone's native contacts app.
Why randomize the URL instead of using the person's name?
A URL like /card/william-rodriguez reveals a naming pattern that anyone can use to enumerate a full team directory by guessing names. A randomly generated slug with no name or structure cannot be guessed. Combined with a noindex tag that keeps the page off search engines, the card is effectively invitation-only.
What happens if a virtual card subscription service shuts down or raises prices? Every QR code that points to that service stops working. Any printed card, email signature, or conference badge that included the QR code becomes useless simultaneously. A self-hosted system on your own domain eliminates this risk entirely: the URL resolves as long as your website runs.
Can Analytical Ants build this for our company? Yes. The system is straightforward to implement on any modern web framework. Setup for the first card takes an afternoon. Each additional team member is a config entry and a file. For organizations with 25 or more employees currently on a virtual card subscription, the first-year savings typically cover the implementation cost. Reach out here to discuss scope.
What is a vCard and how does it work?
A vCard (.vcf) is a standard contact file format supported natively by iOS, Android, and every major desktop operating system. When a user downloads the file, their contacts app opens it automatically and prompts them to save the contact record. No third-party app or account is required. The format supports text fields, URLs, and binary data including embedded profile photos.
Skip this section if you're not building something similar.
Stack: Next.js 15 App Router, TypeScript, Tailwind CSS, Lucide icons.
Route structure: app/card/[slug]/page.tsx as a server component. In Next.js 15, params is a Promise and must be awaited.
Config: lib/cards.ts exports a typed Record<string, CardData> object. generateStaticParams reads the keys and pre-renders all cards at build time. No database or runtime API required.
Slug generation:
node -e "console.log(Math.random().toString(36).slice(2, 10))"
vCard photo embedding using sharp (bundled with Next.js):
node -e "
const sharp = require('sharp');
const fs = require('fs');
sharp('public/images/team/headshot.avif')
.resize(300, 300, { fit: 'cover', position: 'top' })
.jpeg({ quality: 80 })
.toBuffer()
.then(buf => {
const b64 = buf.toString('base64');
const header = 'PHOTO;ENCODING=b;TYPE=JPEG:';
const first = header + b64.slice(0, 75 - header.length);
const rest = b64.slice(75 - header.length).match(/.{1,75}/g).map(l => ' ' + l).join('\r\n');
console.log(first + '\r\n' + rest);
});
"
To add a team member: generate a slug, add an entry to lib/cards.ts, create the .vcf in /public, and point a QR code at https://yourdomain.com/card/<slug>.