Skip to main content
Back to blog
April 3, 20265 min read

Build a Phishing Detector with InboxWatch and Python

A complete Python script that reads .eml files, extracts email headers, and calls the InboxWatch API for threat analysis. Under 30 lines of code.

What you will build

A Python command-line tool that takes an .eml file as input, extracts the security-relevant headers, sends them to the InboxWatch analysis API, and prints a structured threat report. The entire script is under 30 lines.

This is useful for SOC teams that want to triage suspicious emails from a quarantine folder, incident responders analyzing exported messages, or anyone building email security tooling in Python.

Prerequisites

You need Python 3.8+ and the requests library:

pip install requests

You need an InboxWatch API key. Sign up at inboxwatch.ai/check (15 free scans, then $0.10/scan), then create a key in Settings.

Set your API key

Store your API key as an environment variable. This keeps it out of your source code:

# macOS / Linux
export INBOXWATCH_API_KEY="iw_live_your_api_key_here"

# Windows (PowerShell)
$env:INBOXWATCH_API_KEY = "iw_live_your_api_key_here"

# Windows (Command Prompt)
set INBOXWATCH_API_KEY=iw_live_your_api_key_here

The API key is optional. Without it, you get 10 requests per minute on the anonymous tier.

The complete script

Save this as phishing_check.py:

#!/usr/bin/env python3
"""Analyze an .eml file for phishing threats using InboxWatch."""

import sys
import os
import email
import requests

API_URL = "https://inboxwatch.ai/api/mcp/analyze"
API_KEY = os.environ.get("INBOXWATCH_API_KEY", "")

def analyze_eml(path: str) -> dict:
    with open(path, "r", encoding="utf-8", errors="replace") as f:
        msg = email.message_from_file(f)

    payload = {
        "from": msg.get("From", ""),
        "replyTo": msg.get("Reply-To", ""),
        "subject": msg.get("Subject", ""),
        "messageId": msg.get("Message-ID", ""),
        "authenticationResults": msg.get("Authentication-Results", ""),
        "receivedSpf": msg.get("Received-SPF", ""),
        "received": msg.get_all("Received", []),
        "xOriginatingIp": msg.get("X-Originating-IP", ""),
    }

    headers = {"Content-Type": "application/json"}
    if API_KEY:
        headers["Authorization"] = f"Bearer {API_KEY}"

    resp = requests.post(API_URL, json=payload, headers=headers, timeout=10)
    resp.raise_for_status()
    return resp.json()

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: python phishing_check.py <file.eml>")
        sys.exit(1)

    result = analyze_eml(sys.argv[1])

    print(f"\nThreat Score: {result['threatScore']}/100")
    print(f"Threat Level: {result['threatLevel'].upper()}")
    print(f"Verdict:      {result['verdict']}\n")

    for ind in result.get("indicators", []):
        print(f"  [{ind['severity'].upper()}] {ind['description']}")
        if ind.get("evidence"):
            print(f"           Evidence: {ind['evidence']}")

    auth = result.get("authentication", {})
    if auth:
        print(f"\nAuthentication:")
        print(f"  SPF:   {auth.get('spf', 'n/a')}")
        print(f"  DKIM:  {auth.get('dkim', 'n/a')}")
        print(f"  DMARC: {auth.get('dmarc', 'n/a')}")

Usage

Run the script against any .eml file:

python phishing_check.py suspicious_email.eml

Example output for a phishing email:

Threat Score: 85/100
Threat Level: CRITICAL
Verdict:      HIGH RISK: 2 critical threat indicator(s) detected.

  [CRITICAL] From domain uses lookalike characters to impersonate a known domain
             Evidence: g00gle.com resembles google.com
  [HIGH] Reply-To address differs from From and uses a free email provider
             Evidence: From: g00gle.com, Reply-To: gmail.com

Authentication:
  SPF:   fail
  DKIM:  none
  DMARC: fail

How it works

The script does three things:

  1. Parse the .eml file. Python's built-in email module reads the file and provides access to all headers. No additional parsing libraries are needed.
  2. Extract security headers. The script pulls the From, Reply-To, Subject, Message-ID, Authentication-Results, Received-SPF, Received chain, and X-Originating-IP headers. These are the fields the InboxWatch API uses for analysis.
  3. Call the API. A single POST request tohttps://inboxwatch.ai/api/mcp/analyze with the extracted headers. The response includes the threat score, threat level, verdict, authentication results, and detailed indicators.

Batch processing

To scan an entire directory of .eml files, wrap the script in a loop:

import glob

for eml_path in glob.glob("quarantine/*.eml"):
    result = analyze_eml(eml_path)
    score = result["threatScore"]
    level = result["threatLevel"]
    print(f"{eml_path}: {score}/100 ({level})")
    if score >= 70:
        print(f"  >> HIGH RISK: {result['verdict']}")

Your API key supports up to 1,000 requests per minute. For batch processing, the script handles the rate limit automatically via the INBOXWATCH_API_KEYenvironment variable set earlier.

Go further: scan your entire account

This script analyzes individual email headers. For full account protection, InboxWatch scans your entire Gmail or Microsoft 365 configuration: forwarding rules, delegates, OAuth apps, sign-in patterns, and 100+ security checks that run automatically every 30 minutes.

Sign up at inboxwatch.ai/check to run your first scan. 15 scans are free, then $0.10 each via Stripe. Connect your account in 60 seconds, no credit card to start.

Extending the script

Some ideas for building on this foundation:

  • Write results to a CSV file for reporting
  • Send Slack notifications for emails scoring above 70
  • Integrate with your SIEM by forwarding JSON results to a syslog endpoint
  • Add to a CI pipeline to scan test phishing emails as part of security training
  • Build a web interface with Flask or FastAPI that accepts email uploads

Error handling

The API returns standard HTTP status codes:

  • 200 - Analysis successful
  • 400 - Invalid input (missing from field or malformed JSON)
  • 401 - Invalid or expired API key
  • 429 - Rate limit exceeded (check Retry-After header)
  • 500 - Server error

The script uses resp.raise_for_status() to throw an exception on non-200 responses. In production, you should catch requests.exceptions.HTTPErrorand handle rate limiting with a backoff strategy.

Scan your entire inbox for threats

This script checks one email at a time. A full InboxWatch scan checks everything: forwarding rules, delegates, OAuth apps, sign-in activity, and 100+ security checks. 15 free scans, then $0.10 each.

Start Free Scan

15 free scans. No credit card to start. $0.10/scan after.

Written by Nicholas Papadam, founder of InboxWatch. Senior Analyst with 6+ years in enterprise security operations.