A vCard 4.0 file imports cleanly when four things hold: the card opens and closes with the right lines and states its version straight after the opening, it carries a formatted name, every structured field has exactly the number of parts the standard fixes, and special characters inside values are escaped, all as laid out in RFC 6350. Phone numbers have a matching problem. A number is unambiguous only in the international form defined by ITU-T E.164, a leading + and then the dialling code of the country followed by the subscriber digits, and a local number can only be put into that form when the country is known from somewhere other than the digits. This guide goes through the rules of the vCard specification that importers actually trip over, then the phone rules, then a check that can fail.

The frame of the card

The specification, RFC 6350, requires the card to begin with a BEGIN line whose value is VCARD and to end with a matching END line (RFC 6350). The VERSION property must be present, must carry the value 4.0 for this specification and has to sit immediately after the opening line. Older vCard versions let it float anywhere or leave it out, so an exporter written for the old format can produce a card that a strict 4.0 reader has reason to refuse (RFC 6350, VERSION).

The next requirement is the formatted name. The FN property must be in every card, and it holds the name as one piece of display text (RFC 6350, FN). A card with a good structured name and no FN is therefore still invalid.

Names, organisations and addresses

The N property holds the structured name, in five slots with a fixed order: surname, first name, middle names, a title placed before the name, and a suffix placed after it. Semicolons divide the slots (RFC 6350, N). When the card lacks a slot, that slot stays empty but its semicolon stays in place, which is how a card for a person known only by a first name has a leading semicolon. Moving the first name into the surname slot because it "looks like" a surname shifts every later slot by one, and an importer may then file the person under the wrong name.

ORG is structured too. The first component is the organisation and later ones are units inside it (RFC 6350, ORG). The specification's own example escapes the comma of a company name, which brings us to escaping below. TITLE is a single text value and EMAIL a single text value that readers usually treat as an address.

The ADR property has seven slots, in this order: PO box, apartment or suite, street, city, region, postcode and country name. An absent slot keeps its semicolon, and the specification advises leaving the first two empty because of past interoperability trouble (RFC 6350, ADR). Our suggestion, not the specification's, is to put a suite number into the street slot instead of the second one, so that old and new readers see the same address.

Phone numbers inside the card

TEL is free text by default, for compatibility with vCard 3, but the specification wants it switched to a URI value, and it expects the tel scheme of RFC 3966. The TYPE parameter takes values such as voice, cell, fax and text, and voice is assumed when it is absent (RFC 6350, TEL). A line therefore looks like this:

TEL;VALUE=uri;TYPE=cell:tel:+359888123456

The tel URI rules are in RFC 3966. It wants the global form for every number that can have one, and it demands a phone-context parameter on a local number, naming the area where that number is valid (RFC 3966, section 5.1). Spaces are not allowed as visual separators inside the URI, and an extension travels as an ext parameter after the number instead of being appended to the digits (RFC 3966). A card writer that pastes a pretty number such as +44 20 7946 0000 into the URI breaks the first of those rules.

Escaping, folding and line breaks

Three rules about special characters cause most silent damage, and the specification states all of them. Commas inside any value take a backslash, even in properties that never hold lists. A semicolon inside one slot of a structured property such as N or ADR takes a backslash too, since a bare one opens the next slot. Backslashes are doubled, and a line break within a value becomes a backslash plus the letter n. Escaping anywhere else is forbidden (RFC 6350, section 3.4). A company called "Smith, Jones & Co" therefore carries a backslash before its comma, and a street line holding a semicolon needs the same.

Lines end with CRLF, and the text encoding is UTF-8 with no way to override it. Content lines should be folded to at most 75 octets, not counting the line break, by inserting a CRLF followed by one space or tab, and a character that takes several octets must never be split across the fold (RFC 6350, section 3). The count is in bytes, not letters, so a Cyrillic or accented name reaches the limit sooner than a Latin one of equal length. A writer that counts letters folds in the wrong place for those names, and one that cuts through the middle of a multi-byte character produces a card that fails the UTF-8 decoder.

What E.164 asks of a number

E.164 is the ITU-T recommendation that defines the international public numbering plan (ITU-T E.164). In practice the form is a leading +, the dialling code of the country, then the national part of the number, digits only, with no spaces, hyphens, brackets or extension. The libphonenumber project, which documents phone-number pitfalls, notes that the recommendation caps a number at 15 digits including one to three for the country code, and adds that real German numbers longer than that exist (libphonenumber falsehoods). RFC 3966 pulls the other way and says implementations must not assume any maximum, minimum or fixed length (RFC 3966, section 5.1). The safe reading is that 15 digits is a good reason to look twice at a longer string, not a reason to truncate it.

The leading zero and the default country

Most local formats carry a prefix used only for domestic calls, commonly written as a zero. In an international number it normally disappears, and people write (0) after the country code to say so. That is not universal: libphonenumber points out that in Italy the zero became part of the number in 1998 and must stay, so +39 is followed by the zero (libphonenumber falsehoods). A rule such as "always drop the first zero" is therefore wrong for some countries, and "never drop it" is wrong for others. The same page also warns against storing numbers as integers, because a leading zero can be significant and because extensions and dialling characters are not digits.

This is why a bare local number cannot be converted by looking at it. The digits 020 7946 0000 could belong to any country that uses a leading zero; only a stated country turns them into an international number. That country has to come from the request, the form field or the account, and not from the tongue the text is written in or from an e-mail domain, since the libphonenumber page lists several reasons why a number's origin says little about where its owner lives. A converter that has neither country nor plus sign should return nothing and say why. The specification examples are no guide either: the TEL example in RFC 6350 shows a French number whose national zero follows the dialling code directly, a form that a normaliser would not produce (RFC 6350, TEL).

A check that can fail

Build one card with awkward values on purpose and read it back with your own parser, then with a second reader such as a phone's contacts import.

  1. Use a name with a comma and a semicolon, a company with a comma, a street with a semicolon, and a name long enough in Cyrillic to cross the folding limit.
  2. Unfold the lines, split N on unescaped semicolons and confirm five parts; do the same for ADR and confirm seven.
  3. Confirm that line 2 is the version line and that FN is present.
  4. Feed the phone normaliser a string with 16 digits, a number without a leading + and without a default country, and one with an extension. The first should be refused or flagged, because the usual ceiling is 15 digits (libphonenumber falsehoods); the second returned empty with a reason; the third split into number and extension.

Then break each rule once on purpose, for example by deleting one semicolon from N, and confirm that the check fails. If it keeps passing after that deletion, its way of counting slots is blind to the damage, and a pass on a real card says nothing.

For another sort of data that looks fine until it is parsed, see our write-up on converting between CSV and JSON without losing data.

When this does not apply

Only cards of version 4.0 as defined in RFC 6350 are in scope here, together with phone numbers in global form. Many phone and mail clients still read and write vCard 2.1 or 3.0, which differ in version syntax, parameter style and line folding, and which this guide does not cover. Private numbering plans, emergency and service short codes cannot be written in international form and need local notation with a context, as RFC 3966 says. We read the specifications and did not test import behaviour in any particular contacts application, so what a given app does with a card that breaks a rule can differ from the rule. We did not open the recommendation text itself, only its catalog page, so the digit limit comes from the libphonenumber description. The remark about Cyrillic names reaching the fold sooner rests on the usual two-byte size of such letters in UTF-8, which we did not measure.

Two tools for these jobs

The vcard-from-signature skill reads the sign-off lines under a mail message, written in English, Bulgarian, German or another language, and answers with the contact card alone. It fills the name slots only from what the signature shows, leaves out anything it does not give, such as a surname or a country code, writes phone numbers as tel URIs, escapes special characters, and treats legal footers and quoted replies as text that is not contact data.

The phone-e164-normalize skill takes a list of phone numbers and a stated default country, and returns one JSON object per line with the input, the international-form number, a separate extension and a reason when it will not guess. It keeps the trunk zero out where the country drops it and in where the country keeps it, and it leaves the result empty, with the reason, for numbers it cannot read in only one way: a short or overlong string, one that was cut off, two numbers glued together, or a local number when no default country is given.

Read this post as Markdown: /blog/vcard-e164-contact-data-import.md · Atom feed.