pythonhtml-emailsmtplib

Trying to improve - Python emailing code and HTML


I just got assigned the task of creating a code to automatically send graph resports via email. The images should be embedded in to the HTML for better reading and i should only send 1 email per report. I never touched HTML before, buti maneged to come up with the code down below:

def enviar_email():  
    body = """ 
        <html>
        <body>

        <p>Prezados,</p> 
        
        <p>Expectativas IPCA Anual</p>
        <p>
        <img src="cid:image0" alt="Error" width="600" height="300">
        </p>

        <p>Expectativas IPCA Média vs. Meniada</p>
        <p>
        <img src="cid:image1" alt="Error" width="600" height="300">
        <img src="cid:image2" alt="Error" width="600" height="300">
        </p>
        <p>
        <img src="cid:image3" alt="Error" width="600" height="300">
        <img src="cid:image4" alt="Error" width="600" height="300">
        </p>
        <p>
        <img src="cid:image5" alt="Error" width="600" height="300">
        </p>

        <p>Expectativas IPCA Ponderado vs. Ult 5 dias</p>
        <p>
        <img src="cid:image6" alt="Error" width="600" height="300">
        <img src="cid:image7" alt="Error" width="600" height="300">
        </p>
        <p>
        <img src="cid:image8" alt="Error" width="600" height="300">
        <img src="cid:image9" alt="Error" width="600" height="300">
        </p>
        <p>
        <img src="cid:image10" alt="Error" width="600" height="300">
        </p>

        <p>Expectativas IPCA Anual</p>
        <p>
        <img src="cid:image11" alt="Error" width="600" height="300">
        <img src="cid:image12" alt="Error" width="600" height="300">
        </p>
        <p>
        <img src="cid:image13" alt="Error" width="600" height="300">
        </p>

        <p>Expectativas IPCA Mensal</p>
        <p>
        <img src="cid:image14" alt="Error" width="600" height="320">
        <img src="cid:image15" alt="Error" width="600" height="320">
        </p>
        <p>
        <img src="cid:image16" alt="Error" width="600" height="320">
        </p>
        
        <p>Expectativas PIB</p>
        <p>
        <img src="cid:image17" alt="Error" width="600" height="300">
        <img src="cid:image18" alt="Error" width="600" height="300">
        </p>
        <p>
        <img src="cid:image19" alt="Error" width="600" height="300">
        <img src="cid:image20" alt="Error" width="600" height="300">
        </p>         


        <p>Expectativas Taxa Selic</p> 
        <p>
        <img src="cid:image21" alt="Error" width="600" height="300">
        <img src="cid:image22" alt="Error" width="600" height="300">
        </p>
        <p>
        <img src="cid:image23" alt="Error" width="600" height="300">
        <img src="cid:image24" alt="Error" width="600" height="300">
        </p>

        <p>Expectativas Câmbio</p>        
        <p>
        <img src="cid:image25" alt="Error" width="600" height="300">
        </p>      

        <body>
        <html>
        """

    msg = MIMEMultipart()
    msg['Subject'] = 'SUBJECT'
    msg['From'] = 'EMAIL'
    msg['To'] = 'EMAIL'
    password = 'dcau ogjy pdct rqhl'
    msg.attach(MIMEText(body, 'html'))

    *#iterating through diretories to get the images and assigning them a index value*
    index = 0
    main_dir_path = 'K:\Temp\Ramiro\Relatório Focus out'
    sub_dir_path = glob.glob(main_dir_path + '/*')
    for graf_dir_path in sub_dir_path:
        graf_path = glob.glob(graf_dir_path + '/*.png')
        for graf in graf_path:
            with open(graf, 'rb') as fp:
                img = MIMEImage(fp.read(), _subtype='png')
                img.add_header('Content-ID', f'<image{index}>')
                msg.attach(img)
                index += 1

    *# Login Credentials for sending the mail
    smtp = smtplib.SMTP('smtp.gmail.com: 587')
    smtp.starttls()
    smtp.login(msg['From'], password)
    smtp.sendmail(msg['From'], [msg['To']], msg.as_string().encode('utf-8'))
    print('Email enviado')

enviar_email()

The code isn't pretty, but does what it is supposed to do. That sad, i was trying to figure out ways that i can improve it. I wanted to reduce the amount of hardcodded lines in the html section, but i couldn't do it. Any sugestion is very wellcome and sorry for my bad english :).

Email output exemple: The result:


Solution

  • Instead of hardcoding all the <img> tags, you can create the HTML dynamically, especially because you are already looping through directories and the files attached to the images.

    I would start by defining a base HTML structure and insert dynamic content into it, which will make your code more flexible and open for future changes. I would make the template code have the name of the section and then placeholders for each section. (Ex. {image_section_1}).

    I would then create a dictionary (image_sections) where each key is an image_section (or whatever you decided to name the placeholders) and the values are set to empty strings.

    In your for loop, before moving on to the next item I'd add dynamic code in here. I'd set a section_key variable and set it to the f string f'image_section{index // 2 + 1} (because there are 2 images per section and to make the numbering 1 based instead of 0 based because our dict is 1 based)

    Then I'd make a img_tag variable where you have all your image specifications but src would be cid:image{index} (I'd keep the alt, width, height specs the same)

    Then I'd tell my loop to access my dictionary at section key and add the img_tag (image_sections[section_key] += f'<p>{img_tag}</p>)

    Now I'd use that template from earlier (maybe called body_template or whatever) and python's .format() method unpack the dictionary and pass its keys and values as arguments. it would say something like:

    body = body_template.format(**image_sections)
    

    But truly if you implement none of the above PLEASE securely handle the password. It is recommended to use environment variables to avoid hardcoding sensitive data. It's quite easy to set up an environment variable, I know on my mac the command I'd run in my terminal would be:

    export EMAIL_PASSWORD="your-email-password"
    

    then in my code I would set a variable that pointed to my environmental variable:

    password = os.getenv('EMAIL_PASSWORD')
    

    I hope that was a clear enough explanation. Let me know if I can clarify myself further.