Skip to main content
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.

Send a contact card

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",
});

vCard format reference

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

FieldExampleDescription
NDoe;JaneLast name; First name
FNJane DoeFull display name
TEL;TYPE=CELL+15559876543Mobile phone
TEL;TYPE=WORK+15551112222Work phone
EMAILjane@acme.comEmail address
ORGAcme CorpOrganization
TITLEHead of EngineeringJob title
URLhttps://acme.comWebsite
ADR;TYPE=WORK;;123 Main St;City;ST;ZIP;USAddress
NOTEMet at WWDC 2025Free-text note
BDAY1990-06-15Birthday
Include TEL and EMAIL fields for the best experience. iOS shows action buttons for calling, texting, and emailing directly from the contact card preview.

Add a contact photo

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.