Christmas cards using Python and Contacts App. —Help wanted

Automaters Post

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
  • 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[0] == 'mail':
					mail_to = getMailAddress(address[1])
				elif address[0] == '_$!<Home>!$_':
					mail_to = getMailAddress(address[1])
				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[1] for x in person.related_names if x[0] == '_$!<Child>!$_']
			# Gather all spouses
			spouse_list = [x[1] for x in person.related_names if x[0] == '_$!<Spouse>!$_']
			# Gather all girlfriend/boyfriend or significant others with a different last name which are labeled 'partner'
			partner_list = [x[1] for x in person.related_names if x[0] == '_$!<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
				# 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
				# 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
		# Single only
			contact_output += person.full_name
			contact_output += mail_to