1
\$\begingroup\$

I wrote code to automate sending emails using smtp in Python. Below is my code I've written. However, I think there might be a simpler way (simplified code)....could you please review and give me feedback?

In the email list csv file, the first column contains full names (e.g. Joe.Bloke) and the second column contains full email addresses (joe.bloke@company.com)

import smtplib
import pandas

MY_EMAIL = "abc@example.com"
MY_PASSWORD = "abc"

data = pandas.read_csv("email_list.csv")

for name in data.name:
    first_name = name.split('.')[0]
    with open("invitation.txt") as letter:
        contents = letter.read()
        contents = contents.replace("[name]", first_name)

for email in data.email:
    with smtplib.SMTP("smtp.mail.example.com") as connection:
        connection.starttls()
        connection.login(MY_EMAIL, MY_PASSWORD)
        connection.sendmail(from_addr=MY_EMAIL, to_addrs=email, msg= f"Subject: Christmas 
        Party Invitation!\n\n {contents}")
New contributor
Detectivechris is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
\$\endgroup\$
2
  • \$\begingroup\$ The title should just explain what task the code accomplishes, your concerns about the code should not be in the title. Please read How do I ask a good question?. \$\endgroup\$
    – pacmaninbw
    Jan 15 at 2:27
  • \$\begingroup\$ I changed the title so that it describes what the code does per site goals: "State what your code does in your title, not your main concerns about it.". Please check that I haven't misrepresented your code, and correct it if I have. \$\endgroup\$ Jan 15 at 11:02
1
\$\begingroup\$

I think there might be a simpler way

Unfortunately not. It's kind of the opposite: this implementation is "too simple", and doesn't capture most of the things you should care about during email transmission:

  • Include friendly names in your email addresses using email.utils.formataddr
  • Send as HTML, which is done basically 100% of the time instead of plaintext-only. This is best done by making a multipart message that has both plaintext as well as HTML.
  • Consider using a proper template instead of your invitation.txt, using either Jinja2 (if you're feeling fancy) or at least template strings if you want to use a built-in
  • Don't pass from_addr and to_addrs in sendmail; instead build up an email.message.Message object with its own Subject, From and To; and send that using smtp.send_message
\$\endgroup\$
3
  • 1
    \$\begingroup\$ Thank you so much for your answer! \$\endgroup\$ Jan 15 at 5:37
  • 2
    \$\begingroup\$ "Send as HTML" I strongly disagree with. Only do that for recipients that you know prefer it. Usually, any HTML-only mail I receive goes straight in the bin. \$\endgroup\$ Jan 15 at 11:06
  • \$\begingroup\$ @TobySpeight To be clear, I meant a multi-part text+html message and not HTML alone. \$\endgroup\$
    – Reinderien
    Jan 15 at 13:52
1
\$\begingroup\$

I agree with Reinderien that Jinja would be a good options for your templates, and in case you are using Flask it is already installed. Jinja is also used by Ansible.

Using Pandas is overkill imo, and you are loading the whole file to memory. This is acceptable as long as you don't have millions of subscribers, or performance might degrade. You could as well open the file in read mode and loop on it, line by line. A regular open statement is enough. There is also a built-in CSV module in Python you can use, and this one does not require installing any additional package.

You should not have two loops. Fetch the name and E-mail in one pass, line by line, and you can send your mail straight away. Note that you reload the file invitation.txt for every person. That is unnecessary. Just load it once at the start of your script. If you need to replace contents, you can always make a copy to a second variable. It's better than performing repeated file I/O.

It obviously looks like this is a one-shot need, or a short-term project, otherwise I would suggest using a database and add some logging. Log what you sent, to whom and when. At some point you may receive spam complaints and may be asked to justify yourself, and also show proof of opt-in. For business purposes I wouldn't advise using that script.

It may be a good idea to log the responses from your SMTP server in order to investigate errors. The SMTP server may enforce rate limiting (inquire about limits), so the sending of invitations could fail in the middle.

For a Xmas party a plain-text E-mail is terse, so a good HTML template would improve the mood of your recipients. As said above, do multipart with plain text as a fallback. You have some examples here.

Here is a minimalistic example using Jinja:

from jinja2 import Template

# embedded string, but could load from a text file
t = Template("Dear {{ recipient }}")
mail_body = t.render(recipient="joe@doe.com")
print(mail_body)

This will print:

Dear joe@doe.com

So the idea is that you load the template once, but render it with different values in a loop. In fact you would have two templates, one plain-text and one HTML.

\$\endgroup\$
1
  • \$\begingroup\$ Thank you for your answer! Yes, I completely agree that I should use one loop and was trying to figure out how I can do that. I'm a python learner and trying to automate a few office tasks. Thanks for the informative answer. I'll look into jinja. \$\endgroup\$ yesterday
1
\$\begingroup\$

Don't do unnecessary I/O

Currently you're reading the message template for each user. This unnecessarily will drop performance. You can safely read the template once and render it for each user:

with open("invitation.txt") as letter:
    template = letter.read()

for name in data.name:
    first_name = name.split('.')[0]
    contents = template.replace("[name]", first_name)

Don't perform unnecessary reconnects

You can use one connection to send all the emails:

with smtplib.SMTP("smtp.mail.example.com") as connection:
    connection.starttls()
    connection.login(MY_EMAIL, MY_PASSWORD)

    for email in data.email:
        connection.sendmail(from_addr=MY_EMAIL, to_addrs=email, msg= f"Subject: Christmas 
        Party Invitation!\n\n {contents}")
\$\endgroup\$

Your Answer

Detectivechris is a new contributor. Be nice, and check out our Code of Conduct.

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.