📨 Build an Email Validation Tool with Python & Tkinter (Step-by-Step)
Source: Dev.to
📦 Step 1: Import Required Libraries
import sys
import os
import re
import threading
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import dns.resolver # MX record lookup
import sv_ttk # Modern Tkinter theme
Install missing packages
pip install dnspython sv-ttk
🛠 Step 2: Helper Functions
Resolve file paths (useful for packaging later)
def resource_path(file_name):
"""Return absolute path to a resource, works for PyInstaller bundles."""
base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
return os.path.join(base_path, file_name)
Status‑bar updates
def set_status(msg):
"""Update the status bar text."""
status_var.set(msg)
root.update_idletasks()
These helpers keep the UI responsive and inform the user while background tasks run.
🖥 Step 3: Create the Main Application Window
root = tk.Tk()
root.title("Email Validation Tool")
root.geometry("720x680")
sv_ttk.set_theme("light") # Light theme by default
🌍 Step 4: Global State Variables
dark_mode_var = tk.BooleanVar(value=False)
email_var = tk.StringVar()
validation_result_var = tk.StringVar(value="Result: —")
email_history = [] # List of validated emails
These variables will:
- Track dark‑mode state
- Store the user‑entered email address
- Show validation results
- Keep a history of all validated emails
🌗 Step 5: Dark‑Mode Toggle
def toggle_theme():
"""Switch between light and dark themes."""
bg = "#2E2E2E" if dark_mode_var.get() else "#FFFFFF"
fg = "white" if dark_mode_var.get() else "black"
root.configure(bg=bg)
for w in ["TFrame", "TLabel", "TLabelframe",
"TLabelframe.Label", "TCheckbutton"]:
style.configure(w, background=bg, foreground=fg)
email_entry.configure(background=bg, foreground=fg)
The function manually updates widget colours whenever the user toggles dark mode.
📧 Step 6: Email Validation Logic
6.1 Check Email Format (Regex)
def is_valid_email_format(email):
"""Return True if the email matches a simple pattern."""
regex = r"^[\w\.-]+@[\w\.-]+\.\w+$"
return re.match(regex, email) is not None
6.2 Check Domain MX Records
def has_mx_record(domain):
"""Return True if the domain has at least one MX record."""
try:
records = dns.resolver.resolve(domain, "MX")
return bool(records)
except Exception:
return False
6.3 Start Validation (UI Thread)
def validate_email():
"""Validate the email entered by the user."""
email = email_var.get().strip()
if not email:
messagebox.showwarning("Error", "Please enter an email address.")
return
set_status("Validating email...")
threading.Thread(
target=_validate_email_thread,
args=(email,),
daemon=True
).start()
The heavy lifting runs in a background thread so the UI stays responsive.
6.4 Validation Worker Thread
def _validate_email_thread(email):
"""Background worker that performs the actual checks."""
if not is_valid_email_format(email):
validation_result_var.set("Result: ❌ Invalid format")
set_status("Validation complete")
return
domain = email.split("@")[1]
if has_mx_record(domain):
validation_result_var.set("Result: ✅ Valid email")
else:
validation_result_var.set("Result: ⚠ Domain may not receive emails")
add_to_history(email)
set_status("Validation complete")
🗂 Step 7: Email History Management
Add to history list
def add_to_history(email):
"""Append email to history and update the Listbox."""
email_history.append(email)
history_list.insert(tk.END, f"{len(email_history)} • {email}")
Export History to .txt
def export_history_txt():
"""Save the email‑validation history to a text file."""
if not email_history:
messagebox.showinfo("Empty History", "No emails to export.")
return
file_path = filedialog.asksaveasfilename(
defaultextension=".txt",
filetypes=[("Text Files", "*.txt")],
title="Export Email History"
)
if not file_path:
return
try:
with open(file_path, "w", encoding="utf-8") as f:
f.write("Email Validation History\n")
f.write("=" * 30 + "\n\n")
for i, email in enumerate(email_history, 1):
f.write(f"{i}. {email}\n")
set_status("Email history exported")
messagebox.showinfo("Export Successful", "Email history saved successfully.")
except Exception as e:
messagebox.showerror("Export Failed", str(e))
🎨 Step 8: Styling
style = ttk.Style()
style.theme_use("clam") # Base theme
style.configure(
"Action.TButton",
font=("Segoe UI", 11, "bold"),
padding=8
)
Creates bold, nicely padded action buttons.
📊 Step 9: Status Bar
status_var = tk.StringVar(value="Ready")
ttk.Label(
root,
textvariable=status_var,
anchor="w"
).pack(side=tk.BOTTOM, fill="x")
The status bar lives at the bottom of the window and displays messages such as Ready, Validating…, and Export successful.
🧩 Putting It All Together (UI Layout)
Below is a minimal layout that ties the pieces together. Feel free to customise the look and feel.
# ----- Main Frame -------------------------------------------------
main_frame = ttk.Frame(root, padding=20)
main_frame.pack(fill="both", expand=True)
# Email entry
ttk.Label(main_frame, text="Enter email address:").grid(row=0, column=0, sticky="w")
email_entry = ttk.Entry(main_frame, textvariable=email_var, width=40)
email_entry.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
# Validate button
validate_btn = ttk.Button(
main_frame,
text="Validate",
style="Action.TButton",
command=validate_email
)
validate_btn.grid(row=0, column=2, padx=5)
# Result label
result_lbl = ttk.Label(
main_frame,
textvariable=validation_result_var,
font=("Segoe UI", 12)
)
result_lbl.grid(row=1, column=0, columnspan=3, pady=10, sticky="w")
# Dark‑mode toggle
dark_check = ttk.Checkbutton(
main_frame,
text="Dark mode",
variable=dark_mode_var,
command=toggle_theme
)
dark_check.grid(row=2, column=0, pady=5, sticky="w")
# History Listbox
history_frame = ttk.Labelframe(main_frame, text="Validation History")
history_frame.grid(row=3, column=0, columnspan=3, sticky="nsew", pady=10)
history_list = tk.Listbox(history_frame, height=8)
history_list.pack(fill="both", expand=True, padx=5, pady=5)
# Export button
export_btn = ttk.Button(
main_frame,
text="Export History",
style="Action.TButton",
command=export_history_txt
)
export_btn.grid(row=4, column=0, columnspan=3, pady=5)
# Make columns expand nicely
main_frame.columnconfigure(1, weight=1)
root.mainloop()
Run the script, enter an email address, click Validate, and watch the result appear. Toggle dark mode, view your history, and export it whenever you like.
Enjoy building and extending your own email‑validation utility! 🚀
🧱 Step 10: Build the UI Layout
Main Container
main = ttk.Frame(root, padding=20)
main.pack(expand=True, fill="both")
Title & Input
ttk.Label(
main,
text="Email Validation Tool",
font=("Segoe UI", 22, "bold")
).pack()
email_entry = ttk.Entry(
main,
textvariable=email_var,
font=("Segoe UI", 14),
justify="center"
)
email_entry.pack(fill="x", pady=8)
ttk.Label(
main,
textvariable=validation_result_var,
font=("Segoe UI", 12, "bold")
).pack(pady=4)
🎛 Step 11: Controls
controls = ttk.Frame(main)
controls.pack(pady=8)
ttk.Button(
controls,
text="✅ Validate",
command=validate_email,
style="Action.TButton"
).pack(side="left", padx=4)
ttk.Button(
controls,
text="📤 Export History",
command=export_history_txt,
style="Action.TButton"
).pack(side="left", padx=4)
🗄 Step 12: Email History Vault
vault = ttk.LabelFrame(
main,
text="Email History Vault",
padding=10
)
vault.pack(fill="both", expand=True, pady=10)
history_list = tk.Listbox(
vault,
font=("Segoe UI", 10),
height=10
)
history_list.pack(fill="both", expand=True)
⚙ Step 13: App Options
ttk.Checkbutton(
main,
text="Dark Mode",
variable=dark_mode_var,
command=toggle_theme
).pack(pady=6)
▶ Step 14: Run the App
root.mainloop()
This starts the Tkinter event loop and launches your application.
🎉 Final Thoughts
You now have a fully functional email validation desktop app with:
- Regex validation
- MX record checking
- Multithreading
- History tracking
- Export functionality
- Dark mode
