Syncing Fastmail Contacts with vdirsyncer
Posted on
Your contacts data is valuable. Years of accumulated email addresses, phone numbers, and personal information shouldn't be locked away in someone else's cloud service. Even with a trusted provider like Fastmail, having a local backup and the ability to work with your contacts offline is important.
This is where vdirsyncer comes in. It's a command-line tool that synchronizes calendars and contacts between different storage locations using CardDAV and CalDAV protocols. I use it to sync my Fastmail contacts to my laptop, giving me both a backup and local access to my contact data.
Why sync contacts locally?
There are several good reasons to maintain a local copy of your contacts:
- Data ownership: You have direct access to your data in a standard format (vCard)
- Backup: Protection against accidental deletion or service issues
- Offline access: Query and use contacts without an internet connection
- Integration: Use your contacts with local tools and scripts
- Privacy: Keep a copy of your data under your control
Setting up vdirsyncer
vdirsyncer configuration lives in ~/.config/vdirsyncer/config. The setup is straightforward - you define a remote "storage", and a local one, and tell vdirsyncer how to sync between them.
Here's how my config looks:
[general]
# A folder where vdirsyncer can store some metadata about each pair.
status_path = "~/.vdirsyncer/status/"
[pair contacts]
a = "contacts_local"
b = "contacts_remote"
# Synchronize all collections that can be found.
# You need to run `vdirsyncer discover` if new calendars/addressbooks are added
# on the server.
collections = ["from a", "from b"]
# Synchronize the "display name" property into a local file (~/.contacts/displayname).
metadata = ["displayname"]
# To resolve a conflict the following values are possible:
# `null` - abort when collisions occur (default)
# `"a wins"` - assume a's items to be more up-to-date
# `"b wins"` - assume b's items to be more up-to-date
conflict_resolution = null
[storage contacts_local]
type = "filesystem"
path = "~/.contacts/"
fileext = ".vcf"
[storage contacts_remote]
type = "carddav"
url = "https://carddav.fastmail.com/"
username = "<ACCOUNT>"
password.fetch = ["command", "secret-tool", "lookup", "app_id", "vdirsyncer"]
Note that I'm only syncing contact data (CardDAV) at this stage, not calendars (CalDAV) - vdirsyncer can do both though.
Here I have a remote CardDAV storage that connects to Fastmail's servers, and a local filesystem storage that saves the contacts as individual vCard files. The sync happens bidirectionally, so any changes I make locally get pushed back to Fastmail, and any changes on the server get pulled down.
The configuration requires your Fastmail username and an app password for authentication. I'm using the password.fetch option here with secret-tool to retrieve the password from the system keyring (Gnome keyring in my case). This avoids storing the password in a plain text config file.
Querying contacts with khard
Having a local copy of contacts is nice, but you need a way to query them. khard is a command-line contacts manager that works perfectly with vdirsyncer's vCard files.
With khard, you can search your contacts, view details, and even edit them from the command line. The changes sync back to Fastmail the next time vdirsyncer runs. It's a clean, simple interface for managing contact data without leaving the terminal.
Optimizing for email clients
While khard is great for interactive queries, I also needed a format that aerc (my terminal-based mail client) could use for address completion. That's where my contacts sync script comes in:
#!/usr/bin/env python3
import subprocess
# Sync contacts with Fastmail
subprocess.check_call(["vdirsyncer", "sync"])
# Get email list from khard
result = subprocess.check_output(
["khard", "email", "--parsable", "--remove-first-line"],
text=True,
encoding="utf8",
)
# Process output: keep first 2 fields (email address and name) and deduplicate by email address
seen = set()
out = []
for line in result.splitlines():
fields = line.split("\t", 2)[:2]
if not fields:
continue
email = fields[0]
if email not in seen:
seen.add(email)
out.append("\t".join(fields))
with open("all.txt", "wt") as f:
f.write("\n".join(out))
This uses khard to dump out all the email addresses and names from the vCard files and produces an easily searchable contact list. Having aerc search this using ripgrep is much faster than calling khard for email address completion.
Standardization matters
One of the best things about this setup is that it's all based on open standards. CardDAV for syncing, vCard for storage, and plain text for the optimized list. There's no proprietary format or vendor lock-in. If I wanted to switch from Fastmail to another provider, or from aerc to another mail client, the pieces would still fit together.
This approach to contacts management might seem like overkill to some, but for me it strikes the right balance between convenience and control. My contacts are accessible from anywhere via Fastmail's web interface, but I also have a local copy that I can query, backup, and use with local tools. I've been meaning to get this part of my personal life organised for a long time, and I finally have.