Python Central HubSign in

Python Projects / Beginner

Basic Email Sender

Build a Python email sender with Tkinter and smtplib. Learn SMTP, Gmail app passwords, HTML emails, attachments, multiple recipients, secrets management, and how to grow it into a real mailer.

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

Set Up Gmail App Password

  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.

Getting Started

Create the project

  1. Create folder email-sender.
  2. Inside, create emailsender.py.

Write the code

Email SenderSource
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()

Run it

cmd
C:\Users\Your Name\email-sender> python emailsender.py
# GUI opens with sender / receiver / password / message fields and Send button

SMTP in Seven Lines

The core is shorter than the GUI around it:

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

Step-by-Step (GUI Version)

1. Imports

Python
import tkinter as tk
from tkinter import messagebox
import smtplib, re
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

2. Build the form

Python
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

Python
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

To send rich content:

Python
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

Python
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

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

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

Set them once:

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

Python
import keyring
keyring.set_password("PCH-mailer", sender, app_password)     # one-time setup
pwd = keyring.get_password("PCH-mailer", sender)             # later

Common Mistakes

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

Variations to Try

1. CC and BCC

Python
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

Python
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

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

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

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

6. Bounce handling

Read the SMTP response. server.send_message returns a dict of failed recipients.

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

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

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

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

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 From name and reply-to that goes somewhere real.

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

  • 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

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

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.

Was this page helpful?

Let us know how we did