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 1 – Part 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:
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, clickSecurity
. - On the right-hand side of the
Security
page, navigate to2-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 therpt_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.