📨 Build an Email Validation Tool with Python & Tkinter (Step-by-Step)

Published: (February 1, 2026 at 09:56 PM EST)
4 min read
Source: Dev.to

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

Email Validation Tool

Back to Blog

Related posts

Read more »