I store my contacts in an org file called people.org
. The file has the following structure:
* John Doe :PROPERTIES: :ID: some-generated-uuid :GROUP: Work :PHONE: +1234567890 :ADDRESS_HOME: Foo bar street, no 5 :EMAIL: john@doe.com :END: * Dohn Joe :PROPERTIES: :GROUP: High school :PHONE: +1334567890 :END: - Some notes about this person.
The nice part is, it's just plain org-mode. I only use top-level headings in this file, instead of creating header hierarchies. I utilize :GROUP:
property to categorize people, this way a person may belong to multiple categories. I use org-ql if I need to find people related to one group or if I want to filter them based on some specific property.
However the main use case is that I reference these headers in my other org files. For example, I also keep my diary in org-mode and I may write about some event that I participated with John Doe from above. I simply reference (using org links) to that person. The benefit of this is being able to recollect all of your notes about a particular person using one simple search.
Anyway, lets jump how I synchronize these contact information with my phone.
I simply create .vcf
file, a format that most of the contacts apps that are aware of, based on my people.org
file. Then I synchronize this .vcf
file to my phone, using Syncthing. The following snippet creates the .vcf
file.
(defun isamert/build-contact-item (template-string contact-property) (if-let ((stuff (org-entry-get nil contact-property))) (concat (format template-string stuff) "\n") "")) (defun isamert/vcard () "Create a .vcf file containing all contact information." (interactive) (write-region (string-join (org-map-entries (lambda () (string-join `("BEGIN:VCARD\nVERSION:2.1\n" ,(format "UID:urn:uuid:%s\n" (org-id-get nil t)) ,(isamert/build-contact-item "FN:%s" "ITEM") ,(isamert/build-contact-item "TEL;CELL:%s" "PHONE") ,(isamert/build-contact-item "EMAIL:%s" "EMAIL") ,(isamert/build-contact-item "ORG:%s" "GROUP") ,(isamert/build-contact-item "ADR;HOME:;;%s" "ADDRESS_HOME") ,(isamert/build-contact-item "ADR;WORK:;;%s" "ADDRESS_WORK") ,(format "REV:%s\n" (format-time-string "%Y-%m-%dT%T")) "END:VCARD") "")) "LEVEL=1") "\n") nil (read-file-name "Where to save the .vcf file?" "~/Documents/sync/" "contacts.vcf")))
Simply call the isamert/vcard
function in your people.org
file and you get a .vcf
file. By default, it creates the file under ~/Documents/sync
. This folder is automatically synced with my phone using Syncthing. Then I open my contacts app and import the file. That's it.
I used to earliest possible .vcf
format that is available so that every contacts app can import them. You can add/remove fields to your .vcf
export quite easily, just take a look at this wikipedia page for vCard and the relevant line to your function.
Here is an example, just to demonstrate how you obtain/copy email of one of your contacts interactively. A use case might be:
isamert/contacts-select-email
which presents you all of your contact's names.To:
field of your email client.
Don't forget to point find-file-noselect
to your people.org
file.
(defun isamert/contacts-email-alist () "Get an alist of contact name and emails." (seq-filter (lambda (it) it) (org-map-entries (lambda () (when-let ((email (org-entry-get nil "EMAIL"))) `(,(org-entry-get nil "ITEM") . ,email))) "LEVEL=1"))) (defun isamert/contacts-select-email () "Search through your contacts interactively and copy their email." (interactive) (with-current-buffer (find-file-noselect "~/Documents/notes/people.org") (let ((email-alist (isamert/contacts-email-alist))) (kill-new (cdr (assoc (completing-read "Copy email of: " email-alist) email-alist))))))
UID
section to entries so that when you re-import your contacts after an update to the vcf file, already-existing contacts won't get duplicated.