Finding a straight forward way to print envelopes for christmas card mailings has not been an easy task on the mac/ios. Without mail merge support in Pages/Numbers finding a solution has been a creative endeavor. This is what I came up with…
Tools/Requirements:
- iOS Contact database properly formatted
- Pythonista on iOS
- Install the Pythonista Shell (staSH)
- Install FPDF module
Contacts Setup
Names
There will be one main contact card per family to generate the printed envelope. This would generally be the person you want addressed first. Make sure the first name and last name are inputed properly in the contact card. If you want spouses, partners, or children also included, then those names have to be added to the “add relation” section of the contact card.
Any card that you want included needs to have the hashtag #xmas in the note of the card. As long as you keep this up to date then mailings will be easy.
The output would be like this:
- Single: Johnny Appleseed
- Married same last name: Johnny and Mary Appleseed
- Married/Partner/Sig Other different last name: Johnny Appleseed and Mary Smith
- Kids would be added on the line below: and Kid1, Kid2, Kid3
Addresses
Contacts can have multiple addresses. So the script looks for a “home” address. If there is a “home” label and a “mail” label then the “mail” label is used. No address will be used if it can’t find one of these labels.
staSH
The Pythonista SHell needs to be installed so that the FDPF module can be installed into the app.
Go here for installation instructions
FPDF
This is used generate one page of a PDF for each envelope. You can customize the size of each page, the margins, and add graphics.
Using the staSH:
pip install fpdf
That should make it available to your script.
Documentation here
Code
For you to use/review
import contacts
from fpdf import FPDF
# CONSTANTS________
# Ask user for the height and width of envelope
ENVELOPE_H = float(input('Height(inches): '))
ENVELOPE_W = float(input('Width(inches): '))
# This is used for the height of the text cells
HEIGHT = .2
# Adjust these for where you want the addressee text placed on the page. The defaults are 40% from the left and 45% from the top
SENDER_POSITION_X = ENVELOPE_W * .4
SENDER_POSITION_Y = ENVELOPE_H * .45
# FUNCTION DEFS________
def getMailAddress(address_dict):
''' Gets the address dictionary from a contact and returns a tuple of information.
(List of streets,city string,state string,zip string,country string)
'''
street = address_dict['Street'].strip().split('\n')
city = address_dict['City']
state = address_dict['State']
zip = address_dict['ZIP']
country = ''
if address_dict[contacts.COUNTRY] != 'United States':
country = address_dict[contacts.COUNTRY].upper()
the_address = (street,city,state,zip,country)
return the_address
def add_return_address():
''' Adds the return address to the top left of the document.
'''
# Change to your info
pdf.cell(w=0,h=HEIGHT,txt='YOUR NAME',border=0,ln=1)
pdf.cell(w=0,h=HEIGHT,txt='123 Example Street',border=0,ln=1)
pdf.cell(w=0,h=HEIGHT,txt='city, State. ZIP',border=0,ln=1)
#TRY USING AN IMAGE: uncomment the next line and replace the NAME for your image
# The numbers represent the x,y position and the width you want it to be, height auto calulates
#pdf.image('NAME',.3,.3,2.2)
def add_mailing_block(x,h,text):
''' Adds a line to the addressee block.
'''
pdf.set_x(x)
pdf.cell(w=0,h=h,txt=text,border=0,ln=1)
# CREATE THE PDF__________
pdf = FPDF('P','in',(ENVELOPE_W,ENVELOPE_H))
pdf.set_font('Arial',size=12)
pdf.set_auto_page_break(False)
# Gather all contacts
all_ppl = contacts.get_all_people()
# Loop through each person
for person in all_ppl:
# Reset defaults
has_children = False
contact_output = ''
children = ''
mail_to = ''
# Only deal with 'xmas' tagged people
if '#xmas' in person.note:
# Add a new PDF page for this contact
pdf.add_page()
# Collect all addresses in the contact which is a list of tupels in the form [(Label String,Address Dictionary)]
addresses = person.address
if addresses:
for address in addresses:
if address[0] == 'mail':
mail_to = getMailAddress(address[1])
break
elif address[0] == '_$!<Home>!$_':
mail_to = getMailAddress(address[1])
continue
else:
mail_to = (['--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:
has_children = True
children = ', '.join(children_list)
# Married
if spouse_list:
contact_output += person.first_name + ' & ' + ''.join(spouse_list) + ' ' + person.last_name
break
# Has a significant other or different last name
elif partner_list:
contact_output += person.full_name + ' and ' + ''.join(partner_list)
break
# Single with kids
else:
contact_output += person.full_name
# Single
else:
contact_output += person.full_name
add_return_address()
# Move the cursor to the mailing block
pdf.set_xy(SENDER_POSITION_X,SENDER_POSITION_Y)
add_mailing_block(SENDER_POSITION_X,HEIGHT,contact_output)
# Add a line for the kids if necessary
if has_children:
add_mailing_block(SENDER_POSITION_X,HEIGHT,'and '+children)
# Start adding the address lines
for line in mail_to[0]:
add_mailing_block(SENDER_POSITION_X,HEIGHT,line)
# Construct the city state zip string and add to the mail block
city_state_zip = f'{mail_to[1]}, {mail_to[2]} {mail_to[3]}'
add_mailing_block(SENDER_POSITION_X,HEIGHT,city_state_zip)
# Add a line for a country other than the US
if mail_to[4]:
add_mailing_block(SENDER_POSITION_X,HEIGHT,mail_to[4])
# Save the file
pdf.output('envelopes.pdf')
Sample Output
I’ll can try to generate some sample data to show what the output can look like.