Basic Email Sender
Abstract
Section titled “Abstract”Email is older than the web and still drives most automated notifications, password resets, marketing flows, and alerts. In this project you will build a Tkinter GUI that sends real email via Gmail SMTP. We start with a plain-text version, then upgrade to HTML bodies, multiple recipients, file attachments, retries, secrets management, and end with a sketch of a transactional-email service. Along the way you learn why Gmail wants app passwords instead of your real one, and how smtplib lays out an email under the hood.
You will learn:
- The shape of SMTP — connect, STARTTLS, login, MAIL FROM, RCPT TO, DATA, QUIT.
- Why Gmail blocks plain-password logins and how App Passwords fix it.
- How
email.mime.*builds multipart messages. - How to validate input safely and handle network errors.
- How to manage secrets so credentials never sit in source control.
Prerequisites
Section titled “Prerequisites”- Python 3.6 or above.
- A text editor or IDE.
- A Gmail account with 2-Step Verification enabled.
- An App Password generated at myaccount.google.com/apppasswords.
Set Up Gmail App Password
Section titled “Set Up Gmail App Password”- Visit myaccount.google.com/security.
- Enable 2-Step Verification (required to access App Passwords).
- Go to App passwords → generate a new one named “Python Mailer”.
- Copy the 16-character code. This replaces your normal password in scripts.
- Treat it like a password. Anyone with this string can send mail as you.
Getting Started
Section titled “Getting Started”Create the project
Section titled “Create the project”- Create folder
email-sender. - Inside, create
emailsender.py.
Write the code
Section titled “Write the code”Email Sender
pch.viewSource# Basic Email Sender in Python
# Before the using your gmail and password follows these steps:
# 1. Go to your google account
# 2. Click on Security
# 3. Under "Signing in to Google," select 2-Step Verification.
# 4. At the bottom of the page, select App passwords.
# 5. Enter a name that helps you remember where you’ll use the app password.
# 6. Select Generate.
# 7. To enter the app password, follow the instructions on your screen. The app password is the 16-character code that generates on your device.
# 8. Select Done.
# 9. Use Generated Password in the password field
# For More Infomation: https://support.google.com/mail/?p=InvalidSecondFactor
import tkinter as tk # pip install tk
from tkinter import *
import smtplib # pip install smtplib
from tkinter import messagebox
import re
def sendEmail():
try:
sender = email.get()
rec = receiver.get()
pas = password.get()
msg = message.get()
# Validating the Email
if(sender == "Enter Your Email" or rec == "Enter Receiver's Email" or pas == "Enter Your Password" or msg == "Enter Your Message"):
messagebox.showerror("Error", "Please Enter All The Fields")
return
# Validate Email Using Regular Expression
email_regex = re.compile(r"[^@]+@[^@]+\.[^@]+")
if not email_regex.match(sender):
messagebox.showerror("Error", "Invalid Email")
return
if not email_regex.match(rec):
messagebox.showerror("Error", "Invalid Receiver's Email")
return
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.login(sender, pas)
server.sendmail(sender, rec, msg)
server.quit()
messagebox.showinfo("Success", "Email Sent Successfully")
email.delete(0, END)
receiver.delete(0, END)
password.delete(0, END)
message.delete(0, END)
email.insert(0, "Enter Your Email")
receiver.insert(0, "Enter Receiver's Email")
password.insert(0, "Enter Your Password")
message.insert(0, "Enter Your Message")
except Exception as e:
messagebox.showerror("Error", "Something Went Wrong")
print(e)
root = tk.Tk()
root.title("Email Sender")
root.geometry("700x700")
root.config(background="white")
label_file_explorer = Label(root, text = "Email Sender using Tkinter", width = 100, height = 3, fg = "gray", bg = "whitesmoke")
email = Entry(root, width = 50)
email.insert(0, "Enter Your Email")
receiver = Entry(root, width = 50)
receiver.insert(0, "Enter Receiver's Email")
password = Entry(root, width = 50)
password.insert(0, "Enter Your Password")
message = Entry(root, width = 50)
message.insert(0, "Enter Your Message")
button_explore = Button(root, text = "Send Email", command = sendEmail)
exit_button = Button(root, text = "Exit", command = root.destroy)
label_file_explorer.grid(column = 1, row = 1)
email.grid(column = 1, row = 2)
receiver.grid(column = 1, row = 3)
password.grid(column = 1, row = 4)
message.grid(column = 1, row = 5)
button_explore.grid(column = 1, row = 6)
exit_button.grid(column = 1, row = 7)
root.mainloop() Run it
Section titled “Run it”C:\Users\Your Name\email-sender> python emailsender.py
# GUI opens with sender / receiver / password / message fields and Send buttonSMTP in Seven Lines
Section titled “SMTP in Seven Lines”The core is shorter than the GUI around it:
import smtplib
with smtplib.SMTP("smtp.gmail.com", 587) as server:
server.starttls()
server.login("you@gmail.com", "16-char-app-password")
server.sendmail("you@gmail.com", "friend@example.com",
"Subject: Hello\n\nThis is the body.")Step by step:
- Connect to Gmail’s SMTP on port 587.
STARTTLSupgrades the connection to encrypted.- Login with your address + App Password.
sendmaildelivers the message.- The
withblock automatically issuesQUITon exit.
The Subject: header is required for Gmail to show a subject line; everything before the blank line is headers, everything after is the body.
Step-by-Step (GUI Version)
Section titled “Step-by-Step (GUI Version)”1. Imports
Section titled “1. Imports”import tkinter as tk
from tkinter import messagebox
import smtplib, re
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart2. Build the form
Section titled “2. Build the form”root = tk.Tk()
root.title("Email Sender")
root.geometry("520x420")
sender_entry = tk.Entry(root, width=50); sender_entry.pack(pady=5)
receiver_entry = tk.Entry(root, width=50); receiver_entry.pack(pady=5)
password_entry = tk.Entry(root, width=50, show="*"); password_entry.pack(pady=5)
subject_entry = tk.Entry(root, width=50); subject_entry.pack(pady=5)
message_text = tk.Text(root, width=50, height=10); message_text.pack(pady=5)show="*" masks the password as the user types — a small but expected UX detail.
3. Validate and send
Section titled “3. Validate and send”EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
def send():
sender = sender_entry.get().strip()
receivers = [r.strip() for r in receiver_entry.get().split(",") if r.strip()]
pwd = password_entry.get()
subject = subject_entry.get().strip() or "(no subject)"
body = message_text.get("1.0", "end").strip()
if not EMAIL_RE.match(sender):
return messagebox.showerror("Error", "Invalid sender address.")
for r in receivers:
if not EMAIL_RE.match(r):
return messagebox.showerror("Error", f"Invalid recipient: {r}")
if not pwd or not body:
return messagebox.showerror("Error", "Password and body required.")
msg = MIMEMultipart()
msg["From"] = sender
msg["To"] = ", ".join(receivers)
msg["Subject"] = subject
msg.attach(MIMEText(body, "plain"))
try:
with smtplib.SMTP("smtp.gmail.com", 587) as server:
server.starttls()
server.login(sender, pwd)
server.send_message(msg, from_addr=sender, to_addrs=receivers)
except smtplib.SMTPAuthenticationError:
return messagebox.showerror("Auth failed", "Wrong password? Use an App Password.")
except smtplib.SMTPException as e:
return messagebox.showerror("SMTP error", str(e))
except Exception as e:
return messagebox.showerror("Network error", str(e))
messagebox.showinfo("Sent", "Email sent successfully.")MIMEMultipart is the foundation for attachments and HTML; even for plain text it costs almost nothing to use.
HTML Emails
Section titled “HTML Emails”To send rich content:
html = """<h2>Hello!</h2><p>This is <b>HTML</b> email.</p>
<p><a href="https://example.com">Click here</a></p>"""
msg = MIMEMultipart("alternative")
msg.attach(MIMEText("Plain-text fallback", "plain"))
msg.attach(MIMEText(html, "html"))The "alternative" multipart type tells clients to show one of the parts — modern clients use HTML, plain-text-only clients fall back to the text part.
Attachments
Section titled “Attachments”from email.mime.base import MIMEBase
from email import encoders
def attach_file(msg, path):
with open(path, "rb") as f:
part = MIMEBase("application", "octet-stream")
part.set_payload(f.read())
encoders.encode_base64(part)
part.add_header("Content-Disposition",
f'attachment; filename="{os.path.basename(path)}"')
msg.attach(part)
attach_file(msg, "report.pdf")
attach_file(msg, "screenshot.png")Files become base64-encoded MIME parts. Most providers cap attachments around 25 MB.
Secret Management
Section titled “Secret Management”Hard-coding the App Password into emailsender.py is the most common security mistake in this project. Better:
import os
sender = os.environ["EMAIL_USER"]
password = os.environ["EMAIL_PASSWORD"]Set them once:
export EMAIL_USER=you@gmail.com
export EMAIL_PASSWORD=xxxx-xxxx-xxxx-xxxxOr use python-dotenv to read from a .env file (add .env to .gitignore).
For desktop apps, the OS keychain via keyring is the right answer:
import keyring
keyring.set_password("PCH-mailer", sender, app_password) # one-time setup
pwd = keyring.get_password("PCH-mailer", sender) # laterCommon Mistakes
Section titled “Common Mistakes”| Problem | Cause | Fix |
|---|---|---|
| “Username and Password not accepted” | Used real Gmail password | Generate an App Password |
| Mail goes to Spam | No proper From, no DKIM, plain-text body | Use SendGrid/Mailgun for production |
ConnectionRefusedError | Wrong port | 587 for STARTTLS, 465 for smtplib.SMTP_SSL |
| Subject missing | Did not add the header | Set msg["Subject"] |
| Emoji breaks subject | Default encoding mismatch | Use MIMEText(body, _charset="utf-8") |
| Multiple recipients only first receives | Passed string, not list | Split on commas and pass a list |
Variations to Try
Section titled “Variations to Try”1. CC and BCC
Section titled “1. CC and BCC”msg["Cc"] = "manager@example.com"
all_recipients = receivers + ["manager@example.com", "hidden@example.com"]
server.send_message(msg, from_addr=sender, to_addrs=all_recipients)BCC is just a recipient with no header — pass to send_message’s to_addrs but don’t add a header.
2. Personalized mail merge
Section titled “2. Personalized mail merge”for row in csv.DictReader(open("contacts.csv")):
body = template.format(**row)
send(row["email"], body)Read CSV → format template per row → send.
3. Other providers
Section titled “3. Other providers”- Outlook:
smtp-mail.outlook.com:587. - Yahoo:
smtp.mail.yahoo.com:587(also app passwords). - iCloud:
smtp.mail.me.com:587.
4. Inline images
Section titled “4. Inline images”Use MIMEImage and reference with <img src="cid:logo">. The cid: matches a Content-ID header on the image part.
5. Scheduled send
Section titled “5. Scheduled send”Combine with Simple Reminder App — fire send() at a specific time.
6. Bounce handling
Section titled “6. Bounce handling”Read the SMTP response. server.send_message returns a dict of failed recipients.
7. Production-grade with SendGrid / Mailgun
Section titled “7. Production-grade with SendGrid / Mailgun”For more than a few emails per day, switch to a transactional-email provider. They handle deliverability, SPF/DKIM signing, bounce processing, and analytics.
8. CLI mode with argparse
Section titled “8. CLI mode with argparse”mailer.py --to friend@example.com --subject Hi --body "Hello!" --attach report.pdf9. Email server preview with MailHog
Section titled “9. Email server preview with MailHog”For development: run mailhog locally, point SMTP at localhost:1025, see every sent email in a web UI. Never accidentally email real users from a dev script.
10. Sign and encrypt
Section titled “10. Sign and encrypt”S/MIME or PGP/MIME for confidential payloads. Heavy but standardized.
Anti-Spam / Anti-Abuse Notes
Section titled “Anti-Spam / Anti-Abuse Notes”- Gmail caps your sending to ~500 messages/day for personal accounts.
- Sending more than a handful per minute will get you throttled.
- Marketing emails must include an unsubscribe link by law (CAN-SPAM, GDPR).
- Always include a real
Fromname and reply-to that goes somewhere real.
Real-World Applications
Section titled “Real-World Applications”- Account flows — signup confirmations, password resets, 2FA codes.
- System alerts — Cron job failed, disk filling up.
- Marketing — newsletters, drip campaigns (use a provider for these).
- Reports — daily summaries from a script.
- Internal tooling — meeting reminders, expense approvals.
Educational Value
Section titled “Educational Value”- SMTP protocol — the wire format underlying every “send email” library.
- MIME — multipart messages, content types, transfer encodings.
- Secrets management — environment variables, keyring, dotenv.
- Validation — regex for email syntax (note: regex cannot fully validate; SMTP verification is the only certain way).
- Provider quirks — every SMTP server has its own gotchas; abstract the differences.
Next Steps
Section titled “Next Steps”- Move credentials into environment variables or
keyring. - Add attachments and HTML body.
- Wire up a CSV-driven mail merge.
- Replace
smtplibwithyagmailfor a cleaner Gmail-specific API. - Move to SendGrid or Mailgun for transactional use.
Conclusion
Section titled “Conclusion”You built a real email-sending tool from scratch, learned why Gmail demands App Passwords, and saw how MIME multiparts model attachments and HTML bodies. The same patterns scale all the way up to corporate email pipelines — smtplib is the standard tool from beginner scripts to systems sending millions of messages a day. Full source on GitHub. Find more automation projects on Python Central Hub.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading