Many consider business cards to be a thing of the past, but I’m yet to see a better way to share my contact details. Digital alternatives require cooperation from the partner, which is never easier than grabbing a piece of paper.
While business cards are still the best medium to be handed over, their lifecycle is rather limited. When I receive a card, I typically store the information on my phone and hand it back or throw it away within minutes. As my own batch of cards were due for a refill, I decided to try something new to optimize them for this usage.
The digital business card format Link to heading
The dominant digital representation of a contact information is the vCard format.1 Despite the concerning discussions online about the support for newer vCard versions, my own testing has shown that all recent phones import name, email and phone number without any issues from all of 2.1, 3.0, and 4.0 versions.2 As the standard for vCard 4.0 has been published in 2011, this is somewhat expected.
Each version of the vCard standard has a set of required fields. As I’m more concerned about minimizing size than maximizing compatibility, I’ve chosen the version with the least required fields, vCard 4.0.
BEGIN:VCARD
VERSION:4.0
FN:Zsolt Herczeg
TEL:+36207755680
EMAIL:herczegzsolt@herczegzsolt.hu
END:VCARD
Opting for version 4.0 is an interesting choice. The Android contacts app produces vCard 2.1, whereas the Google Contacts website and the iOS contacts app export vCard 3.0. I’ve found that many generators would create a non-compliant vCard 3.0 with missing fields, rather than a compliant vCard 4.0. There must be a compatibility-related reason for such hacks. I’ve tested such a non-compliant card myself but found no practical difference from a compliant vCard 4.0.
The NDEF data storage Link to heading
Just printing the vCard text on the card would be a great joke, but it’d not add much convenience. I could add it in a QR code, but it takes up printable space, and I find them ugly.
Instead of printing the vCard on the card, I can store it inside the card with NFC. The NFC-forum defined NDEF format allows me to store arbitrary data inside a standardized tag, which would be automatically read and processed by a smartphone when held near.3 The list of standardized tags include Ultralight, NTAGX, ST25TN, DESFire and more, notably missing MIFARE Classic Chips.
MIFARE Classic chips would offer the largest storage size with up to 4096 bytes. Even though they are not standardized, they are used widely enough that all devices would read them. I’ve still decided against them, because their cryptographic algorithm is fully broken.4 There will be no business-card rickrolls under my watch.
After disqualifying MIFARE Classic chips, I’ve settled for a MIFARE Ultralight EV1, with mere 128 bytes of storage. My vCard data is 102 bytes, 10 bytes are required to indicate the format as text/vcard
, and there’s 2+2 bytes overhead. Totaling 117 bytes, this data fits on the card and works great… with Android.
Workaround Apple NDEF limitations Link to heading
Apple iOS has limited support for NDEF-formatted tags. It supports opening URIs from NDEF cards, but it does not support reading vCard data directly. It is still possible to share a vCard via NFC, by hosting the file on a webserver and storing the URI of that on the card. This method works well for iOS devices as long as they have internet access. Unfortunately, URI-based sharing breaks the Android experience. Those phones will download and save the vCard as a file, instead of opening it with Contacts.
It is possible to create an NDEF-formatted tag which works well with both systems. A tag may contain multiple records. The operating system will read and process the first supported record, and ignore the rest. This behavior can be relied on to deliver the preferred record for both systems. Store the vCard data in the first record, followed by the web-hosted URI in the next one. Android will open the vCard data from the card, meanwhile iOS will ignore it and open the web-hosted vCard file from the URI.
Hosting the vCard file Link to heading
There’s a problem specific to my card and chip selection: I only have 11 usable bytes remaining. Can I store an NDEF URI in 11 bytes? Just barely. A URI record has an overhead of 5 bytes. This overhead includes a selection of 36 optional prefixes, one of which is https://
. This leaves space for 6 characters of domain name and path. As long as I am willing to register a dedicated domain name for this, it will work. 5
For the hosted vCard file to work properly, I need to ensure it is sent with the correct MIME type. I’ve found that Content-Type: text/vcard; charset=utf-8
works great.6 Interestingly, this is not the default configuration of nginx.
First, I need to make sure that nginx is configured to use the text/vcard
mime type for vcf file extensions. The types section is usually included from the /etc/nginx/mime.types
file where need to add a line like this:7
types {
# ...
text/vcard vcf;
# ...
}
I also need to make sure that utf-8 is sent as a parameter, and that the vCard file is served instead of an index page. These could be done in the server section:
server {
# serve the vCard file instead of homepage
index index.vcf;
# use utf-8 as encoding for text/vcard, in addition to the default types
charset utf-8;
charset_types text/html text/xml text/plain text/vnd.wap.wml application/javascript application/rss+xml text/vcard;
location / {
# attempt to serve a vcard file when listing a directory
try_files $uri $uri/index.vcf @missing;
}
# ...
}
Otherwise, my new domain is configured like any other nginx-hosted static site.
Writing the physical card Link to heading

Screenshot of NFC Tools Pro
All that is remaining is to write the NDEF data to my cards. I’ve opted for an Android phone with NFC Tools Pro as the simplest option. Note that this is one of the apps which generate non-compliant vCard version 3 data by default, I had to create the vCard record manually.
Before I wrap this up, I’d like to share one controversial opinion. It is time to forget the traditional business card size.
A credit card is 85.6mm x 53.98mm. The standard business card size is 89mm x 51mm, just different enough to not fit inside a credit card holder. It is rare to carry around an assortment of business cards, but everybody has a credit card on them. If a business card fits in the same wallet, it may just spend some useful time there before it’s inevitable trip to the trash bin.
Compatibility tests were done with Android 16 and iOS 18.5. ↩︎
Unfortunately, the NFC-forum specifications are purchase-only for steep prices. I must rely on second-hand information such as GoToTags ↩︎
Since the crypto1 cipher has been reverse-engineered the hacking of MIFARE Classic cards has been widely practiced and demonstrated at places such as blackhat workshops. The issues are unlikely to affect a business card use-case, but I prefer to gain experience with something more generally useful. ↩︎
It is technically valid to have a three character domain name, but it’s not feasible to register one under the current ICANN rules. The list of TLDs do not contain any single letter options, and most registries do not allow single-letter domains. This assumes a minimum of 5 characters for a
[domain].[tld]
string. Since appending a path requires the additional character of the/
path separator, it is not possible to fit a meaningful path in an 11 byte long NDEF record. ↩︎There seem to be multiple MIME-types advised by different sources. The RFC6350 section 10.1 recommends the type
text/vcard
with an optional charset parameter. The only allowed charset is utf-8, making the parameter meaningless, but useful for compatibility. Previous standards like RFC2425 required the charset parameter for vCard-like data structures. ↩︎The default mime.types file is available in the GitHub repository of nginx. ↩︎