I have been trying to automate my Christmas card mailing for what seems like years now. I feel like I’m getting closer, but wanted to get some suggestions from the crowd. Here is what I got.
I already have a database of contacts on my phone/Mac via the contacts app so my goal was to utilize that. Since my contacts are set up as individuals and not families, a script was necessary to extract the data and format it properly.
I chose Python and Pythonista for its ability to access and manipulate the contacts database. I considered using the shortcuts app (for being less of an entry barrier), but I can’t access the relation’s names in shortcuts.
Here are the assumptions for my script:
- The tag “#xmas” goes in the notes section of contacts you want to send a card to
- Only apply the tag to the person you want to address first for multiple addressees, ie. having a spouse or partner. So for a husband and wife, customarily the husband is listed first. The spouse’s name will be listed in the related names.
- A name, address, and relatives are entered
- An address looks for a “home” or “mail” label, “mail” takes priority if entered
- Here are the addressing formats for contacts:
- Single: [Full Name]
ex. Johnny Appleseed
- With Spouse: [Primary first name] & [Spouse first name] [Last name]
ex. Johnny & Mary Appleseed
- With a partner: [Full Name] & [Partner]
A partner could be a girlfriend, boyfriend, or even a married person with a different last name. A partner is listed as “partner” in the related name section:
ex. Johnny Appleseed & Mary
ex. John Smith & Mary Cook
- Single: [Full Name]
- If Children are listed they are added below the main addressees. Children are labeled as “child” in the related names
ex. John Appleseed
and Joe, Mary
I am not a programmer, just a hobbyist. So the script could be improved, I’m sure.
Using the contacts database allows me to update addresses and relatives as they happen, then just run the script when needed.
The next problem is what to do with the output. Currently the output is mostly proof of concept as it isn’t able to be printed. I’m open to suggestions for how to get these to print to an envelope. Pages doesn’t have a native mail merge, but maybe it could be applied to a template somehow.
I considered outputting to HTML or LaTeX, which might be overkill.
I’ll share the script for those that want to play around.
import contacts # Function definitions def getMailAddress(address_dict): street = address_dict['Street'].replace('/n','\n').strip() city = address_dict['City'] state = address_dict['State'] zip = address_dict['ZIP'] country = '' if address_dict[contacts.COUNTRY] != 'United States': country = '\n' + address_dict[contacts.COUNTRY] mail_to = '\n' + street + '\n' + city + ', ' + state + ' ' + zip + country return mail_to # Put all contacts in a variable all_ppl = contacts.get_all_people() # Loop through each person for person in all_ppl: # Reset defaults contact_output = '' children = '' mail_to = '' # Only deal with 'xmas' tagged people if '#xmas' in person.note: addresses = person.address if addresses: for address in addresses: if address == 'mail': mail_to = getMailAddress(address) break elif address == '_$!<Home>!$_': mail_to = getMailAddress(address) continue else: mail_to = '\n--No valid address--' # Look for related names # ie. have spouses, partner, kids if person.related_names: # Gather all children children_list = [x for x in person.related_names if x == '_$!<Child>!$_'] # Gather all spouses spouse_list = [x for x in person.related_names if x == '_$!<Spouse>!$_'] # Gather all girlfriend/boyfriend or significant others with a different last name which are labeled 'partner' partner_list = [x for x in person.related_names if x == '_$!<Partner>!$_'] for relation_tuple in person.related_names: # Make a child list if exists if children_list: children = ', '.join(children_list) # Single with kids if not spouse_list and not partner_list: contact_output += person.full_name if children_list: contact_output += '\nand ' + children contact_output += mail_to print(contact_output) print() break # Married with or without kids elif spouse_list: contact_output += person.first_name + ' & ' + ''.join(spouse_list) + ' ' + person.last_name if children_list: contact_output += '\nand ' + children contact_output += mail_to print(contact_output) print() break # Has a significant other or different last name spouse with or without kids elif partner_list: contact_output += person.full_name + ' and ' + ''.join(partner_list) if children_list: contact_output += '\nand ' + children contact_output += mail_to print(contact_output) print() break # Single only else: contact_output += person.full_name contact_output += mail_to print(contact_output) print()