Skip to content

Basic Email Sender

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.
  1. Visit myaccount.google.com/security.
  2. Enable 2-Step Verification (required to access App Passwords).
  3. Go to App passwords → generate a new one named “Python Mailer”.
  4. Copy the 16-character code. This replaces your normal password in scripts.
  5. Treat it like a password. Anyone with this string can send mail as you.
  1. Create folder email-sender.
  2. Inside, create emailsender.py.
Email Sender pch.viewSource
Email Sender
# 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()
command
C:\Users\Your Name\email-sender> python emailsender.py
# GUI opens with sender / receiver / password / message fields and Send button

The core is shorter than the GUI around it:

core.py
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:

  1. Connect to Gmail’s SMTP on port 587.
  2. STARTTLS upgrades the connection to encrypted.
  3. Login with your address + App Password.
  4. sendmail delivers the message.
  5. The with block automatically issues QUIT on 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.

emailsender.py
import tkinter as tk
from tkinter import messagebox
import smtplib, re
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
emailsender.py
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.

emailsender.py
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.

To send rich content:

html.py
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.

attach.py
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.

Hard-coding the App Password into emailsender.py is the most common security mistake in this project. Better:

env.py
import os
sender = os.environ["EMAIL_USER"]
password = os.environ["EMAIL_PASSWORD"]

Set them once:

bash
export EMAIL_USER=you@gmail.com
export EMAIL_PASSWORD=xxxx-xxxx-xxxx-xxxx

Or 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:

keychain.py
import keyring
keyring.set_password("PCH-mailer", sender, app_password)     # one-time setup
pwd = keyring.get_password("PCH-mailer", sender)             # later
ProblemCauseFix
“Username and Password not accepted”Used real Gmail passwordGenerate an App Password
Mail goes to SpamNo proper From, no DKIM, plain-text bodyUse SendGrid/Mailgun for production
ConnectionRefusedErrorWrong port587 for STARTTLS, 465 for smtplib.SMTP_SSL
Subject missingDid not add the headerSet msg["Subject"]
Emoji breaks subjectDefault encoding mismatchUse MIMEText(body, _charset="utf-8")
Multiple recipients only first receivesPassed string, not listSplit on commas and pass a list
cc_bcc.py
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.

merge.py
for row in csv.DictReader(open("contacts.csv")):
    body = template.format(**row)
    send(row["email"], body)

Read CSV → format template per row → send.

  • Outlook: smtp-mail.outlook.com:587.
  • Yahoo: smtp.mail.yahoo.com:587 (also app passwords).
  • iCloud: smtp.mail.me.com:587.

Use MIMEImage and reference with <img src="cid:logo">. The cid: matches a Content-ID header on the image part.

Combine with Simple Reminder App — fire send() at a specific time.

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.

cli
mailer.py --to friend@example.com --subject Hi --body "Hello!" --attach report.pdf

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.

S/MIME or PGP/MIME for confidential payloads. Heavy but standardized.

  • 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 From name and reply-to that goes somewhere real.
  • 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.
  • 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.
  • Move credentials into environment variables or keyring.
  • Add attachments and HTML body.
  • Wire up a CSV-driven mail merge.
  • Replace smtplib with yagmail for a cleaner Gmail-specific API.
  • Move to SendGrid or Mailgun for transactional use.

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.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading