Part 3: Email a PDF in Python

Rate this post

Story: This series of articles assume you are an employee of the City of Sacramento’s IT Department.

At the end of each month, a CSV file is sent to the Chief of Police outlining the crime statistics for the current month.

However, the Chief prefers to view the output in a styled PDF format based on a District/Beat of his choosing.

Part 3 of this series is a continuation of Part 1Part 2 and focuses on:

  • creating an email.
  • adding in the following to the email:
    • the sender’s email address
    • the receiver’s email address
    • the email subject
    • the email body
    • attaching the PDF created in Part 2
  • sending the email with the attachment.

Preparation

This article assumes you have completed the following from Part 1:

  • Installed the Pandas library.
  • Created the CSV outlined in Part 1.
  • Created the PDF outlined in Part 2.

Add the following code to the top of each code snippet. This snippet will allow the code in this article to run error-free.

import pandas as pd
from fpdf import FPDF
import csv
import datetime
import yagmail

Update Gmail Settings

In this article, we create an email. This email contains a subject, body message, and a PDF file.

💡 Note: To email from a Corporate Account, omit these instructions.

Gmail has settings that prevent sending emails via code. To circumvent this issue:

  • Navigate to and log in to your Gmail account.
  • On the top-right icon, click to select Manage your Google Account.
  • On the left-hand side of the Manage your Google Account page, click Security.
  • On the right-hand side of the Security page, navigate to 2-step verification.
  • On the Less secure app access page, turn off this option.

After completing these steps, you are now ready to send an email with an attachment.

💡 Note: After running this code, turn the 2-step verification back on.


Setup Email Variables

This section declares variables for sending the email.

fname = f'{rpt_num}.pdf'
esender   = 'youremail@gmail.com'
ereceiver = 'chiefpolice@cityofsacramento.com'
esubject  = 'Crime Stats Report'

ebody = '''
        <p>Please find attached the Crime Stats PDF file as requested.</p>
        <p>Regards,</p>
        <p>Matt Bond</p>
       '''
  • Line [1] creates the fname variable containing the rpt_num selected in Part 1. The extension ‘.pdf’ is appended to the variable.
  • Line [2] creates the esender variable containing your email address.
  • Line [3] creates the ereceiver variable containing the receiver’s email address.
  • Line [4] creates the esubject variable containing the email subject.
  • Line [5] creates the ebody variable containing the body of the email.

Create & Send the Email

This section formulates the email, attaches the PDF, and sends it.

def send_yagmail(ereceiver, esubject, ebody, fname):
    yag = yagmail.SMTP(esender, 'yourgmailpassword')
    yag.send(to=ereceiver, subject=esubject, contents=ebody, attachments=fname)

send_yagmail(ereceiver, esubject, ebody, fname)
print(f'The email was sent to {esender}.')

Validate the Code

The email created above is sent to the Gmail account, ereceiver.

After navigating to and opening the Gmail account, everything works as expected.

Output

Great job!


Finishing Up

Below is the complete code from Part 1, Part 2, and this article, Part 3.

import pandas as pd  
from fpdf import FPDF
import csv
import datetime
import yagmail

cols = ['cdatetime', 'address', 'district', 'beat', 'grid', 'crimedescr']
df   = pd.read_csv('crimes.csv', usecols=cols)
df.sort_values('cdatetime', inplace=True, ascending=True)

df['beat'] = df['beat'].str.rstrip()
df = df.apply(lambda x: x.astype(str).str.title())

lst = '123456ABCQ'
rpt_num = None

while True:
    rpt_num = input('Select a District/Beat (1A-6C or Q to quit): ').upper()
    if rpt_num == 'Q':
        exit()
    elif rpt_num[0] not in lst[0:6] or rpt_num[1] not in lst[6:9]:
        print('You entered an invalid selection!')
    else:
        break

print(f'Report {rpt_num} generating!')

the_filter = (df.query(f"beat == '{rpt_num}'"))
filt_cols=['cdatetime','address','grid','crimedescr']
the_filter.to_csv(f'{rpt_num}.csv', columns=filt_cols)

print(f'Report {rpt_num}.csv resides in the current working directory!')

with open(f'{rpt_num}.csv', 'r') as csvfile:
    data_list = list(csv.reader(csvfile))[1:]

pdf_name = f'{rpt_num}.pdf'
rpt_hdgs = ['Row #', 'Date/Time', 'Address', 'Grid', 'Description'] 
cwidths  = [20, 40, 50, 30, 55]
rpt_font_sz = 7
hdg_font_sz = 11
line_height = 6

class PDF(FPDF):
    def header(self):
        today         = datetime.date.today()
        date_fmt      = today.strftime("%B" " " "%d" ", " "%Y")
        self.l_margin = 6
        self.r_margin = 6
        
        self.set_font('Arial', '', rpt_font_sz)
        self.image('sacramento_logo.png', 10, 8, 36)
        
        self.cell(80)
        self.set_font('Arial', '', hdg_font_sz)
        self.set_text_color(43,60,102)
        self.cell(30, 3, f'District/Beat: {rpt_num}', 0, 0, 'C')

        self.set_font('Arial', '', rpt_font_sz)
        self.cell(-30, 11, f'{date_fmt}', 0, 0, 'C')
        self.ln(12)

        self.set_fill_color(240,248,255)
        col = 0
        while col < len(rpt_hdgs):
            col_width = cwidths[col]
            self.cell(col_width, line_height, rpt_hdgs[col], 0, 0, fill=True)    
            col += 1   
        self.ln(12)    

    def footer(self):
        # self.set_y(-15)
        self.set_font('Arial', 'I', rpt_font_sz)
        self.set_fill_color(240,248,255)
        self.cell(0, line_height, 'Report Page ' + str(self.page_no()) + '/{nb}', 0, 0, 'C', fill=True)

def convert_to_pdf(data_list):
    pdf = PDF()
    pdf.alias_nb_pages()
    pdf.add_page()
    pdf.set_font('Arial', '', rpt_font_sz)

    row_count = 0
    while row_count < len(data_list):
        col = 0
        for c in cwidths:
            pdf.cell(c, 0, data_list[row_count][col], align='L', border=0)
            col += 1
        pdf.ln(4)
        row_count += 1
    pdf.output(pdf_name, 'F')
convert_to_pdf(data_list)

fname = f'{rpt_num}.pdf'
esender   = 'asender.coder@gmail.com'
ereceiver = 'areceiver@gmail.com'
esubject  = 'Crime Stats Report'

ebody = '''
        <p>Please find attached the Crime Stats PDF file as requested.</p>
        <p>Regards,<br/>
        Matt Brody</p>
       '''

def send_yagmail(ereceiver, esubject, ebody, fname):
    yag = yagmail.SMTP(esender, 'F*YTax&obkK^&VuS!!!@')
    yag.send(to=ereceiver, subject=esubject, contents=ebody, attachments=fname)

send_yagmail(ereceiver, esubject, ebody, fname)
print(f'The email has been sent to {esender}.')

Summary

In this article, you learned how to:

  • Create an email
  • Set the sender, receiver, subject of the email
  • Attach a PDF to the email
  • Send the email

🧩 Challenge: The Finxter Challenge is to write additional code to cc the email to another user.