DEV Community

Mate Technologies
Mate Technologies

Posted on

SpamSentinel v1.0 – Build a GUI Email Spam Detector in Python

In this tutorial, we’ll build SpamSentinel, a fast email spam detector with a GUI using Python. This tool can scan .eml and .txt email files, calculate a spam score, and help you manage spam emails effectively.

We’ll cover:

Setting up the environment

Creating utility functions

Building the spam detection worker

Designing the GUI

Adding file selection and drag & drop

Connecting the worker to the GUI

Exporting results

Running the application

  1. Setup: Install required libraries

We’ll need:

tkinter (built-in)

ttkbootstrap (for modern UI)

tkinterdnd2 (optional, for drag & drop support)

Install the extras using pip:

pip install ttkbootstrap
pip install tkinterdnd2

  1. Utility function

We need a helper to get resource paths, especially for icons when packaged with PyInstaller.

import os
import sys

def resource_path(file_name):
"""
Get the path to a resource, works with PyInstaller.
"""

    base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, file_name)
Enter fullscreen mode Exit fullscreen mode

Explanation:
This function ensures your icon or other files can be found whether running as a script or a packaged executable.

  1. Spam detection worker

We’ll create a class to handle scanning emails and calculating a spam score.

import re
from collections import Counter, deque

class SpamWorker:
    def __init__(self, files, min_confidence, include_words, exclude_words, regex_pattern, max_results, callbacks):
        self.files = files
        self.min_confidence = min_confidence
        self.include_words = include_words
        self.exclude_words = exclude_words
        self.regex_pattern = re.compile(regex_pattern, re.IGNORECASE) if regex_pattern else None
        self.max_results = max_results
        self.callbacks = callbacks
        self._running = True

        self.spam_patterns = [
            re.compile(r"(free money|win cash|click here|urgent|lottery)", re.I),
            re.compile(r"(prize|offer|risk-free|credit card)", re.I),
        ]

    def stop(self):
        self._running = False

    def spam_score(self, text):
        score = 0
        for pattern in self.spam_patterns:
            if pattern.search(text):
                score += 50
        return min(score, 100)
Enter fullscreen mode Exit fullscreen mode

Explanation:

spam_score calculates a fake spam score for demonstration.

We use regex patterns to detect typical spam keywords.

stop() allows the process to be canceled.

  1. Running the spam worker

We now scan files and calculate scores:

    def run(self):
        total_files = len(self.files)
        counters = Counter()
        results_buffer = deque(maxlen=self.max_results)

        for i, path in enumerate(self.files):
            if not self._running:
                break
            try:
                with open(path, "r", encoding="utf-8", errors="ignore") as f:
                    content = f.read()
                    if self.include_words and not any(w in content.lower() for w in self.include_words):
                        continue
                    if self.exclude_words and any(w in content.lower() for w in self.exclude_words):
                        continue
                    if self.regex_pattern and not self.regex_pattern.search(content):
                        continue

                    score = self.spam_score(content)
                    if score < self.min_confidence:
                        continue

                    counters["SPAM"] += 1
                    counters["TOTAL"] += 1
                    results_buffer.append((path, score))

                    if "found" in self.callbacks:
                        self.callbacks["found"](path, score)

                    if counters["TOTAL"] >= self.max_results:
                        break
            except Exception:
                pass

            if total_files > 0 and "progress" in self.callbacks:
                self.callbacks["progress"](int((i + 1) / total_files * 100))
            if "stats" in self.callbacks:
                self.callbacks["stats"](dict(counters))

        if "stats" in self.callbacks:
            self.callbacks["stats"](dict(counters))
        if "finished" in self.callbacks:
            self.callbacks["finished"]()
Enter fullscreen mode Exit fullscreen mode

Explanation:

Loops over files and calculates spam scores.

Supports include/exclude word filters and optional regex.

Reports progress and statistics via callbacks for the GUI.

  1. GUI: Setup with ttkbootstrap

We’ll use ttkbootstrap for a modern dark-themed GUI.

import tkinter as tk
import ttkbootstrap as tb
from ttkbootstrap.constants import *

class SpamSentinelApp:
    APP_NAME = "SpamSentinel"
    APP_VERSION = "1.0"

    def __init__(self):
        self.root = tb.Window(themename="darkly")
        self.root.title(f"{self.APP_NAME} v{self.APP_VERSION}")
        self.root.minsize(1200, 700)
        self._build_ui()

    def _build_ui(self):
        main = tb.Frame(self.root, padding=10)
        main.pack(fill=tk.BOTH, expand=True)

        tb.Label(main, text=f"🧠 {self.APP_NAME} - Enterprise Spam Detector",
                 font=("Segoe UI", 22, "bold")).pack(pady=(0, 4))
Enter fullscreen mode Exit fullscreen mode

Explanation:

tb.Window(themename="darkly") creates a dark-themed window.

Label displays the app name and description.

  1. File selection & drag & drop

We allow users to select folders or drag & drop files:

from tkinter import filedialog
try:
    from tkinterdnd2 import TkinterDnD, DND_FILES
    DND_ENABLED = True
except ImportError:
    DND_ENABLED = False

    def browse_files(self):
        folder = filedialog.askdirectory(title="Select Email Folder")
        if folder:
            print("Folder selected:", folder)
Enter fullscreen mode Exit fullscreen mode

Explanation:

TkinterDnD enables drag & drop.

filedialog.askdirectory allows folder selection.

  1. Connecting GUI & Worker

We connect buttons to start scanning and update the GUI with results:

import threading

def start_scan(self):
    selected_files = ["emails/test1.eml", "emails/test2.txt"]  # Example
    min_conf = 50
    self.worker_obj = SpamWorker(
        selected_files,
        min_conf,
        include_words=[],
        exclude_words=[],
        regex_pattern="",
        max_results=1000,
        callbacks={
            "found": lambda f,s: print(f"{f} -> {s}%"),
            "progress": lambda p: print(f"Progress: {p}%"),
            "stats": lambda stats: print(stats),
            "finished": lambda: print("Scan finished")
        }
    )
    threading.Thread(target=self.worker_obj.run, daemon=True).start()
Enter fullscreen mode Exit fullscreen mode

Explanation:

Uses a separate thread to avoid freezing the GUI.

Updates are sent back to the GUI via callbacks.

  1. Exporting results

We allow users to save selected results:

def export_results(self, results):
    path = filedialog.asksaveasfilename(defaultextension=".txt")
    if path:
        with open(path, "w", encoding="utf-8") as f:
            for r in results:
                f.write(f"{r[0]} | Spam Score: {r[1]}%\n")
        print("Export completed!")
Enter fullscreen mode Exit fullscreen mode

Explanation:

Saves selected files with their spam score to a text file.

  1. Run the App

Finally, run the application:

if __name__ == "__main__":
    app = SpamSentinelApp()
    app.root.mainloop()
Enter fullscreen mode Exit fullscreen mode

Explanation:

Starts the GUI loop.

Users can now drag & drop emails, scan them, and export results.

GitHub Link

You can clone the full project here:
https://github.com/rogers-cyber/python-tiny-tools/tree/main/Spam-email-detector-GUI

This structure makes it beginner-friendly, because each part is explained and separated into small, digestible steps with code blocks.

Top comments (0)