You can send contact cards (vCards) as iMessage attachments. The recipient sees a
rich contact preview they can tap to save directly to their contacts.
Create a .vcf file and host it at a public URL, then send it as an attachment:
import { createClient } from "@messages-dev/sdk";
const client = createClient();
await client.sendMessage({
from: "+15551234567",
to: "+15559876543",
file: "https://your-server.com/contacts/jane-doe.vcf",
});
Upload with the file API
Don’t have a server to host .vcf files? Use the file upload API to upload contact cards directly, then pass the returned URL as the attachment:
import { createClient } from "@messages-dev/sdk";
const client = createClient();
const vcard = [
"BEGIN:VCARD",
"VERSION:3.0",
"N:Doe;Jane",
"FN:Jane Doe",
"TEL;TYPE=CELL:+15559876543",
"EMAIL:jane@acme.com",
"END:VCARD",
].join("\r\n");
// Upload the vCard file
const file = await client.uploadFile({
file: new Blob([vcard]),
filename: "jane-doe.vcf",
mimeType: "text/vcard",
});
// Send it as an attachment
await client.sendMessage({
from: "+15551234567",
to: "+15559876543",
file: file.url,
});
Generate a vCard dynamically
You don’t need static files. Generate the vCard on the fly and serve it from your API:
import { createClient } from "@messages-dev/sdk";
const client = createClient();
function buildVCard(contact: {
firstName: string;
lastName: string;
phone?: string;
email?: string;
org?: string;
}) {
const lines = [
"BEGIN:VCARD",
"VERSION:3.0",
`N:${contact.lastName};${contact.firstName}`,
`FN:${contact.firstName} ${contact.lastName}`,
];
if (contact.org) lines.push(`ORG:${contact.org}`);
if (contact.phone) lines.push(`TEL;TYPE=CELL:${contact.phone}`);
if (contact.email) lines.push(`EMAIL:${contact.email}`);
lines.push("END:VCARD");
return lines.join("\r\n");
}
// Serve the vCard from an endpoint
app.get("/contacts/:id.vcf", (req, res) => {
const contact = getContact(req.params.id);
res.setHeader("Content-Type", "text/vcard");
res.send(buildVCard(contact));
});
// Send it
await client.sendMessage({
from: "+15551234567",
to: "+15559876543",
text: "Here's Jane's contact info:",
file: "https://your-server.com/contacts/jane-doe.vcf",
});
A vCard is a plain-text file with the .vcf extension. Here’s a full example:
BEGIN:VCARD
VERSION:3.0
N:Doe;Jane
FN:Jane Doe
ORG:Acme Corp
TITLE:Head of Engineering
TEL;TYPE=CELL:+15559876543
TEL;TYPE=WORK:+15551112222
EMAIL:jane@acme.com
URL:https://acme.com
ADR;TYPE=WORK:;;123 Main St;San Francisco;CA;94105;US
NOTE:Met at WWDC 2025
END:VCARD
Common fields
| Field | Example | Description |
|---|
N | Doe;Jane | Last name; First name |
FN | Jane Doe | Full display name |
TEL;TYPE=CELL | +15559876543 | Mobile phone |
TEL;TYPE=WORK | +15551112222 | Work phone |
EMAIL | jane@acme.com | Email address |
ORG | Acme Corp | Organization |
TITLE | Head of Engineering | Job title |
URL | https://acme.com | Website |
ADR;TYPE=WORK | ;;123 Main St;City;ST;ZIP;US | Address |
NOTE | Met at WWDC 2025 | Free-text note |
BDAY | 1990-06-15 | Birthday |
Include TEL and EMAIL fields for the best experience. iOS shows action
buttons for calling, texting, and emailing directly from the contact card preview.
Embed a base64-encoded photo in the vCard:
BEGIN:VCARD
VERSION:3.0
N:Doe;Jane
FN:Jane Doe
PHOTO;ENCODING=b;TYPE=JPEG:/9j/4AAQSkZJRg...
END:VCARD
import { readFileSync } from "fs";
const photo = readFileSync("jane.jpg").toString("base64");
const vcard = [
"BEGIN:VCARD",
"VERSION:3.0",
"N:Doe;Jane",
"FN:Jane Doe",
`PHOTO;ENCODING=b;TYPE=JPEG:${photo}`,
"END:VCARD",
].join("\r\n");
Keep photos small (under 100KB) for the best delivery experience. Large photos
increase the vCard file size significantly due to base64 encoding.