SEC Filing Alert Email
Get email notifications when a company files a new 10-K, 10-Q, or 8-K with the SEC.
In this tutorial
What You'll Build
A Python script that monitors SEC filings for a watchlist of companies and sends you an email whenever a new 10-K (annual report), 10-Q (quarterly report), or 8-K (current event) is filed. The script tracks which filings it has already seen using a local JSON file, so you only get notified about new ones.
When you run it on a schedule (e.g., every hour via cron), you get a hands-off alert system that tells you when companies you care about file important documents with the SEC.
Prerequisites
- Python 3.8+ installed
- The
requestslibrary:pip install requests - A stockdata.dev API key (free tier works)
- A Gmail account with an App Password (or a Resend API key — see Enhancements)
Gmail App Passwords: Go to your Google Account → Security → 2-Step Verification → App passwords. Generate one for "Mail" and use it in place of your regular password. This works even with 2FA enabled.
The Code
Create a file called filing_alerts.py and paste the following:
import json import os import smtplib from datetime import datetime from email.mime.text import MIMEText import requests # --- Configuration --- API_KEY = "your_api_key_here" BASE_URL = "https://api.stockdata.dev/v1" GMAIL_USER = "you@gmail.com" GMAIL_APP_PASSWORD = "xxxx xxxx xxxx xxxx" ALERT_TO = "you@gmail.com" WATCHLIST = ["AAPL", "MSFT", "GOOGL", "TSLA"] FORMS = ["10-K", "10-Q", "8-K"] STATE_FILE = os.path.expanduser("~/.filing_alerts.json") # --- State management --- def load_state(): if os.path.exists(STATE_FILE): with open(STATE_FILE) as f: return json.load(f) return {} def save_state(state): with open(STATE_FILE, "w") as f: json.dump(state, f, indent=2) # --- Fetch filings from API --- def get_filings(ticker, form_type): resp = requests.get( f"{BASE_URL}/company/{ticker}/filings", headers={"X-API-Key": API_KEY}, params={"form": form_type, "limit": 5} ) resp.raise_for_status() return resp.json().get("filings", []) # --- Send email --- def send_email(subject, body): msg = MIMEText(body) msg["Subject"] = subject msg["From"] = GMAIL_USER msg["To"] = ALERT_TO with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server: server.login(GMAIL_USER, GMAIL_APP_PASSWORD) server.send_message(msg) print("Email sent.") # --- Main logic --- def main(): state = load_state() new_filings = [] for ticker in WATCHLIST: for form_type in FORMS: filings = get_filings(ticker, form_type) state_key = f"{ticker}:{form_type}" last_seen = state.get(state_key) for filing in filings: if last_seen and filing["filing_date"] <= last_seen: break new_filings.append({ "ticker": ticker, "form": form_type, "date": filing["filing_date"], "url": filing["edgar_url"] }) # Update state to most recent filing date if filings: state[state_key] = filings[0]["filing_date"] save_state(state) if new_filings: body = "New SEC Filings Detected\n\n" for f in new_filings: body += f"{f['ticker']}\n" body += f" {f['form']} filed {f['date']}\n" body += f" {f['url']}\n\n" send_email(f"SEC Alert: {len(new_filings)} new filing(s)", body) print(f"Found {len(new_filings)} new filing(s).") else: print("No new filings.") if __name__ == "__main__": main()
How It Works
The Filings Endpoint
The script calls GET /v1/company/{ticker}/filings for each ticker and form type. The endpoint accepts these parameters:
form— Filter by form type (e.g.,10-K,10-Q,8-K)limit— Number of filings to return (we use 5 to catch anything missed)
A typical response looks like this:
{
"ticker": "AAPL",
"filings": [
{
"form": "10-Q",
"filing_date": "2026-02-10",
"report_date": "2025-12-28",
"accession_number": "0000320193-26-000012",
"primary_document": "aapl-20251228.htm",
"edgar_url": "https://www.sec.gov/Archives/edgar/data/320193/000032019326000012/aapl-20251228.htm"
}
]
}
State Tracking
The script stores the most recent filing date for each ticker-form combination in ~/.filing_alerts.json. On each run, it compares incoming filings against these dates. Anything newer triggers an alert. The state file looks like:
{
"AAPL:10-K": "2025-11-01",
"AAPL:10-Q": "2026-02-10",
"AAPL:8-K": "2026-01-30",
"MSFT:10-K": "2025-08-01"
}
On the very first run, the state file does not exist yet, so every filing returned by the API counts as "new." After that first run, only filings with dates newer than the last-seen date will trigger alerts.
Tip: If you want to skip the initial flood of alerts on the first run, run the script once manually and then discard the email. The state file will be populated, and subsequent runs will only alert on truly new filings.
Running It
Replace the configuration values at the top of the script with your own API key and Gmail credentials, then run it:
python filing_alerts.py
On the first run, you will see output like:
Found 7 new filing(s).
Email sent.
The email you receive will look like this:
New SEC Filings Detected AAPL 10-Q filed 2026-02-10 https://www.sec.gov/Archives/edgar/data/320193/... TSLA 8-K filed 2026-02-09 https://www.sec.gov/Archives/edgar/data/1318605/...
On subsequent runs with no new filings:
No new filings.
Scheduling with Cron
To run the script automatically every hour during market hours (Monday through Friday, 9 AM to 6 PM Eastern), add a cron entry:
crontab -e
Add this line:
0 9-18 * * 1-5 /usr/bin/python3 /home/you/filing_alerts.py >> /home/you/filing_alerts.log 2>&1
This runs at the top of each hour from 9 AM to 6 PM, Monday through Friday. Output is appended to a log file so you can check for errors.
Use full paths in cron. Cron does not load your shell profile, so use the absolute path to both Python and your script. Run which python3 to find the correct path on your system.
Enhancements
Use Resend Instead of Gmail
If you prefer not to use Gmail, Resend is a developer-friendly email API with a generous free tier (100 emails/day). Replace the send_email function:
import requests RESEND_API_KEY = "re_your_key_here" def send_email(subject, body): requests.post( "https://api.resend.com/emails", headers={"Authorization": f"Bearer {RESEND_API_KEY}"}, json={ "from": "alerts@yourdomain.com", "to": ["you@gmail.com"], "subject": subject, "text": body } ) print("Email sent via Resend.")
Send to Slack Instead
Post alerts to a Slack channel using an Incoming Webhook:
SLACK_WEBHOOK = "https://hooks.slack.com/services/T.../B.../xxx" def send_slack(text): requests.post(SLACK_WEBHOOK, json={"text": text})
Monitor More Form Types
Expand the FORMS list to catch other important SEC filings:
- Form 4 — Insider transactions (buys and sells by executives)
- 13F-HR — Institutional holdings (quarterly hedge fund disclosures)
- S-1 — IPO registration statements
- DEF 14A — Proxy statements (executive compensation, shareholder votes)
FORMS = ["10-K", "10-Q", "8-K", "4", "13F-HR"]
Note: Form 4 filings are very frequent for large companies. If you add it to your watchlist, expect more emails. Consider filtering for only purchases above a certain dollar amount in a follow-up enhancement.