0

I am making a program so that emails can easily be sent to a list of people with a unique file going to each recipient. This program reads in a list in the form of a .csv file with the last name, first name, email address, and respective attachment for each recipient, uses a template to customize each email with the recipient's name, and attaches their file.

The code can be seen below. I used placeholders for a the subject line and email address and hid the actual text of the email, but you should be able to get the idea. The email_data variable is where the .csv file containing the recipient information is initially loaded into.

import csv
import datetime
import sys

import smtplib
import yagmail

import tkinter as tk
from tkinter import filedialog
from tkinter import simpledialog

root = tk.Tk()
root.withdraw()

password = simpledialog.askstring("Password", "Enter password:", show='*')

current_date = datetime.datetime.now()

subject_line = f'xyz {current_date.strftime("%B")} {current_date.year} xyz File'

LNAMES = []
FNAMES = []
EMAILS = []
FILES = []

yag = yagmail.SMTP('xyz@xyx.com', password)

email_data = filedialog.askopenfilename(filetypes=[('.csv', '.csv')],
                                        title='Select the Email Data file')


def send_email():

    with open(email_data) as csv_file:
        csv_reader = csv.reader(csv_file, delimiter=',')
        line_count = 0

        for row in csv_reader:
            last_name = row[0]
            first_name = row[1]
            email = row[2]
            file = row[3]

            LNAMES.append(last_name)
            FNAMES.append(first_name)
            EMAILS.append(email)
            FILES.append(file)

            line_count += 1

    try:
        for first_name, last_name, email, file in zip(FNAMES, LNAMES, EMAILS, FILES):
            txt = '...'
            yag.send(to=email,
                     subject=subject_line,
                     contents=[txt, file])

            print("Email(s) sent successfully")
            input("Press Enter to exit")
            sys.exit(1)

    except smtplib.SMTPAuthenticationError:
        print("Incorrect Email password entered")
        input("Press Enter to exit")
        sys.exit(1)


send_email()

The issue that I run into is that if the file listed is not in the working directory, the email is sent with a piece of text at the bottom of the email that says something like "file.xlsx" instead of an actual attachment. Is there a way to throw an exception if the file isn't found so the email isn't just sent with no real attachment?

Alex
  • 27
  • 7

3 Answers3

0

You can write your own exceptions:

class EmailAttachmentNotFoundException(Exception):
     pass

and then in your code that attaches the file: (removed the rest of the code for clarity):

def send_email():

with open(email_data) as csv_file:
    csv_reader = csv.reader(csv_file, delimiter=',')
    line_count = 0

    for row in csv_reader:
        last_name = row[0]
        first_name = row[1]
        email = row[2]
        file = row[3]
        if not os.isfile(file):
            raise EmailAttachmentNotFoundException('Could not find file {} Aborting!!!!'.format(file))

        LNAMES.append(last_name)
        FNAMES.append(first_name)
        EMAILS.append(email)
        FILES.append(file)

        line_count += 1

If you want to capture it down the line:

try:
    send_email()
except EmailAttachmentNotFoundException as e:
    # Modify the way the exception is raised and how it is captured, but this is the most basic way of getting what is inside 
    print(str(e))

This prints:

'Could not find file myfile.txt Aborting!!!!'

E.Serra
  • 1,495
  • 11
  • 14
  • This was the solution that worked best for me. I ended up doing a try-except similar to what I did with the password exception. Is there a way to list the file that wasn't found if I use a try-except statement such as `except EmailAttachmentNotFoundException:` ? – Alex Jun 07 '19 at 16:17
  • I think what you are saying is you want to bubble the EmailAttachmentNotFoundException and capture it elsewhere, then see what file failed? You can capture the exception and then use it as well, will edit the answer to make it clear. – E.Serra Jun 10 '19 at 08:21
  • That was exactly what I was trying to say. Your updated solution worked perfectly; thank you so much. – Alex Jun 10 '19 at 17:25
0

Alternatively, if you don't want an exception you can just skip that row:

with open(email_data) as csv_file:
    csv_reader = csv.reader(csv_file, delimiter=',')
    line_count = 0

    for row in csv_reader:
        last_name = row[0]
        first_name = row[1]
        email = row[2]
        file = row[3]
        if not os.isfile(file):
            continue             # <-----

        LNAMES.append(last_name)
        FNAMES.append(first_name)
        EMAILS.append(email)
        FILES.append(file)

        line_count += 1

The continue will skip the rest of the code and go back to the for loop. You could add a print statement just above the continue if you want to see feedback of which ones did not exist.

brechmos
  • 1,278
  • 1
  • 11
  • 22
0

Use

import os

if os.path.exists(file_path):
    do_someting()
else:
    raise SOME_ERROR()

to see if the file exists.

AdamZhang
  • 221
  • 1
  • 3