BotFucker is a local-first inbox defense cockpit for filtering unsolicited sales outreach, generic AI-generated pitches, and repeated CRM follow-ups without handing the keys to your mailbox to a half-baked robot.
The project is intentionally simple: standard-library Python, deterministic classification, local SQLite review state, an audit trail, a browser review queue, and a strict provider boundary. BotFucker imports normalized mail-shaped JSON, lets a human review decisions locally, and saves provider-side execution for an explicit future bridge.
The current core is split into reusable modules under botfucker/:
models.pynormalizes provider-specific mail into stable input/output objects.classifier.pyreturns structured deterministic classifications with reasons.history.pytracks sender history, warning counts, and strike levels in SQLite.review_store.pypersists local review queue items and audit events in SQLite.bridge_ledger.pyprovides a durable bridge ledger scaffold keyed by approved-actionaudit_idvalues.bridge_rehearsal.pyproves emergency-stop and dry-run bridge behavior without provider execution.webhook_contract.pynormalizes bounded n8n/webhook email JSON into local review items.review_cli.pyprovides a provider-safe local review workflow around seeded/imported items.responses.pycontains human-reviewable warning templates.cli.pykeeps the IMAP proof-of-concept behavior behind the existing wrapper.
See DESIGN.md for the proposed architecture and roadmap.
- Accepts normalized email-shaped input from local files, n8n/webhook exports, or the legacy IMAP proof-of-concept path.
- Detects common cold outreach phrases like "quick call", "scale your business", and "wondering if you saw my last".
- Looks for generic AI-pitch markers such as overly formal structure, vague value propositions, and missing personal references.
- Produces structured classification results with reasons and recommended actions.
- Tracks sender/domain history and strike levels locally in SQLite.
- Persists a durable local review queue and audit log.
- Runs a local browser cockpit for reviewing, approving, dismissing, whitelisting, or blacklisting items in local SQLite state.
- Imports bounded n8n/webhook JSON after the provider layer has already fetched mail.
- Exports approved local audit events as an idempotent JSON bundle for an n8n/provider bridge.
- Provides an inactive n8n approved-action bridge starter that validates/dedupes actions in dry-run mode.
- Provides a Phase 14 durable bridge ledger scaffold for recording processed
audit_idstate before provider mutation. - Provides a Phase 15 dry-run bridge rehearsal that proves emergency stop and duplicate-skip behavior before provider mutation.
- Documents the selected Phase 16 sandbox target: Microsoft Outlook warning-draft creation only, with no send-reply mutation.
- Provides a Phase 17 inactive/manual n8n Outlook warning-draft scaffold with a disabled, unconnected Graph
createReplyplaceholder. - Keeps provider credentials and live mailbox side effects outside the local UI and review queue.
The current product path is local-review-first and fails closed.
BotFucker does not need OAuth, IMAP passwords, SMTP passwords, or provider credentials to run the local review cockpit.
Local UI and review CLI actions do not:
- send replies
- move email
- delete email
- archive email
- call Gmail/Microsoft/IMAP/SMTP
- update a real provider whitelist or blacklist
- expose secrets in the browser
The legacy IMAP scanner still exists behind outreach_filter.py, but live automation requires both --live and --auto-approve. The preferred current path is n8n/provider fetch → normalized JSON → local SQLite review → human decision → approved-action export → n8n approved-action bridge dry run.
- Python 3.10 or newer
- No third-party Python packages required
- Optional: n8n or another provider-side workflow to fetch mail and write normalized JSON
- Optional legacy path: an email account with IMAP/SMTP access if you are intentionally using
outreach_filter.pydirectly
Clone the repo:
git clone https://github.com/Jdelg718/BotFucker.git
cd BotFuckerCreate a local blacklist file if you plan to use the legacy scanner:
cp blacklist.example.txt blacklist.txtThe current recommended demo path uses fake/local data only. No mailbox credentials, OAuth tokens, or provider setup required.
python3 -m py_compile outreach_filter.py botfucker/*.py
python3 -m unittest discover -s tests -v
rm -f botfucker_review.sqlite3
python3 -m botfucker.review_cli --db botfucker_review.sqlite3 seed-samples
python3 -m botfucker.local_ui --host 127.0.0.1 --port 8765 --db botfucker_review.sqlite3Open:
http://127.0.0.1:8765/
This demonstrates the local cockpit, durable review queue, sender history, and audit trail without touching a live inbox. Which is the sane order. Weird how that keeps coming up.
A branded FF2K/HyperFrames product explainer lives in:
promo/botfucker-animated-explainer/
Current rendered cut:
promo/botfucker-animated-explainer/renders/botfucker-animated-explainer_narrated-final.mp4
It uses the same FF2K hero art and local-first safety copy as the browser cockpit: no live sends, no deletes, no OAuth in the BotFucker core, human review first, provider bridge later.
Preview/check/render from that folder:
npm run dev
npm run check
npm run renderOnly configure these environment variables if you are intentionally using the older outreach_filter.py IMAP/SMTP path. They are not needed for the local review UI, n8n import workflow, or current provider-boundary design.
Linux/macOS:
export BF_IMAP_HOST="imap.example.com"
export BF_IMAP_PORT="993"
export BF_SMTP_HOST="smtp.example.com"
export BF_SMTP_PORT="465"
export BF_EMAIL_ADDRESS="you@example.com"
export BF_EMAIL_PASSWORD="your-app-password"
export BF_WHITELIST_DOMAINS="yourcompany.com,trustedpartner.com"
export BF_WHITELIST_CONTACTS="person@example.com,client@example.com"PowerShell:
$env:BF_IMAP_HOST="imap.example.com"
$env:BF_IMAP_PORT="993"
$env:BF_SMTP_HOST="smtp.example.com"
$env:BF_SMTP_PORT="465"
$env:BF_EMAIL_ADDRESS="you@example.com"
$env:BF_EMAIL_PASSWORD="your-app-password"
$env:BF_WHITELIST_DOMAINS="yourcompany.com,trustedpartner.com"
$env:BF_WHITELIST_CONTACTS="person@example.com,client@example.com"Optional settings:
export BF_INBOX_FOLDER="INBOX"
export BF_SALES_FOLDER="Junk/Sales"
export BF_BLACKLIST_FILE="blacklist.txt"
export BF_HISTORY_DB="botfucker_history.sqlite3"Gmail:
BF_IMAP_HOST=imap.gmail.com
BF_SMTP_HOST=smtp.gmail.com
Outlook / Microsoft 365:
BF_IMAP_HOST=outlook.office365.com
BF_SMTP_HOST=smtp.office365.com
Yahoo:
BF_IMAP_HOST=imap.mail.yahoo.com
BF_SMTP_HOST=smtp.mail.yahoo.com
The local browser UI supports exactly one explicit storage mode per run:
--sample-data— deterministic fake data in memory for demos/tests.--db PATH— durable local SQLite review queue items and audit events.
Running the UI with neither mode, or with both modes, fails closed. Neither mode connects to IMAP/SMTP/OAuth providers, sends replies, moves/deletes/archives mail, or changes a real provider whitelist/blacklist. UI actions are local review decisions only.
Run the local sample UI:
python3 -m botfucker.local_ui --host 127.0.0.1 --port 8765 --sample-dataSeed a durable local SQLite queue, then run the UI against it:
python3 -m botfucker.review_cli --db botfucker_review.sqlite3 seed-samples
python3 -m botfucker.local_ui --host 127.0.0.1 --port 8765 --db botfucker_review.sqlite3Import webhook/n8n-shaped JSON into SQLite, then review it in the UI:
python3 -m botfucker.review_cli --db botfucker_review.sqlite3 import-webhook-json n8n-messages.json
python3 -m botfucker.local_ui --db botfucker_review.sqlite3Then open:
http://127.0.0.1:8765/
Available local JSON endpoints:
GET /api/dashboard— dashboard counts, safety mode flags,storage_mode, and SQLite DB basename when using--db.GET /api/review-queue— sample or durable SQLite review items. Optional?status=pendingor?status=actionedfilters are supported.GET /api/senders— sender history derived from local queue items and local audit events.GET /api/audit-events— in-memory sample audit log or durable SQLite audit log.GET /api/settings— safety settings, including human approval enabled, YOLO disabled, and storage mode.POST /api/actions— records a local review action only. Supported actions:approve_warning,dismiss,whitelist_sender,blacklist_sender.
Safety posture:
- Human approval is enabled.
- YOLO mode is visible but disabled.
- Provider authentication is not performed by the local UI.
- Sample mode actions are in-memory mock simulations only.
- SQLite mode actions update only
botfucker_review.sqlite3review status/audit rows; they do not perform provider-side effects. - The review DB should not contain secrets, raw auth tokens, passwords, or private provider headers.
YOLO warning copy shown in the UI: “YOLO mode lets BotFucker reply/block without asking you first. This can save time and also make you look like an unhinged mailbox goblin if configured badly. Start conservative.”
Phase 3 adds a durable, local-only SQLite review queue plus a CLI workflow. This is not provider auth and it is not live mailbox automation. The review CLI never connects to IMAP/SMTP/OAuth providers, never sends mail, never moves/deletes/archives mail, and never changes a real provider whitelist or blacklist. Approvals are local review approvals only; they record that a human approved a proposed warning in SQLite, but they do not send the warning.
Seed deterministic fake/sample items:
python3 -m botfucker.review_cli --db botfucker_review.sqlite3 seed-samplesList pending local review items:
python3 -m botfucker.review_cli --db botfucker_review.sqlite3 list --status pending
python3 -m botfucker.review_cli --db botfucker_review.sqlite3 list --status pending --jsonRecord local review decisions:
python3 -m botfucker.review_cli --db botfucker_review.sqlite3 approve sample-001 --actor you --note "approved local warning draft"
python3 -m botfucker.review_cli --db botfucker_review.sqlite3 dismiss sample-002 --actor you
python3 -m botfucker.review_cli --db botfucker_review.sqlite3 whitelist-sender sample-003 --actor you
python3 -m botfucker.review_cli --db botfucker_review.sqlite3 blacklist-sender sample-004 --actor youShow durable local audit history:
python3 -m botfucker.review_cli --db botfucker_review.sqlite3 audit
python3 -m botfucker.review_cli --db botfucker_review.sqlite3 audit --jsonImport local review items from JSON (a list of item objects, or { "items": [...] }):
python3 -m botfucker.review_cli --db botfucker_review.sqlite3 import-json review_items.json
cat review_items.json | python3 -m botfucker.review_cli --db botfucker_review.sqlite3 import-json -Durable queue notes:
- Re-importing the same
item_idis idempotent and does not duplicate items. - Re-importing preserves
pending/actionedhuman review status and audit history. - The local SQLite DB must not contain secrets, tokens, passwords, or real mailbox credentials.
- Sample data uses reserved documentation domains only.
Phase 4 adds a normalized JSON contract for messages that n8n or another provider-side workflow has already fetched. In this flow, n8n owns Gmail/Microsoft/IMAP credentials and maps mail into bounded JSON; BotFucker only imports that JSON into the local SQLite review queue. There is no HTTP listener, OAuth setup, provider auth, sending, moving, deleting, archiving, whitelisting, or blacklisting in this adapter.
Import n8n/webhook JSON from a file or stdin:
python3 -m botfucker.review_cli --db botfucker_review.sqlite3 import-webhook-json n8n-messages.json
cat n8n-messages.json | python3 -m botfucker.review_cli --db botfucker_review.sqlite3 import-webhook-json -Accepted payload shapes include a single message object, a list of message objects, or an object with items, events, or messages arrays. Required fields are a stable message id, sender email, received timestamp, and subject and/or bounded preview/body text. The importer redacts secret-looking values, drops raw headers, truncates long snippets, classifies deterministically, and rejects invalid batches without partial import.
See docs/webhook-contract.md for the JSON examples and n8n mapping guidance.
Phase 6 adds an importable starter workflow and operator guide for the safe n8n path:
docs/n8n-workflow.json— inactive n8n workflow starter with a provider-fetch placeholder, normalization node, file write, and local CLI import command.docs/n8n-workflow.md— setup, mapping, environment variables, smoke test, and safety checklist.
The workflow keeps the same provider boundary as the webhook contract: n8n fetches mail and BotFucker imports bounded JSON into local SQLite review state. It does not send, move, delete, archive, whitelist, blacklist, or run live mailbox actions.
Typical local loop:
export BOTFUCKER_REPO="/path/to/BotFucker"
export BOTFUCKER_REVIEW_DB="/path/to/botfucker_review.sqlite3"
export BOTFUCKER_N8N_MESSAGES="/path/to/n8n-messages.json"
cd "$BOTFUCKER_REPO"
python3 -m botfucker.review_cli --db "$BOTFUCKER_REVIEW_DB" import-webhook-json "$BOTFUCKER_N8N_MESSAGES"
python3 -m botfucker.local_ui --host 127.0.0.1 --port 8765 --db "$BOTFUCKER_REVIEW_DB"Phase 7 documents how provider auth should arrive later without shoving OAuth tokens into the local review UI like a raccoon hiding snacks in an engine bay.
See docs/provider-auth-plan.md.
Phase 7 does not implement Gmail OAuth, Microsoft OAuth, IMAP password handling, YOLO mode, or send/move/delete provider calls. It defines:
- n8n-first versus direct OAuth tradeoffs
- secret storage requirements
- browser/server boundaries
- approved action export shape
- future n8n action bridge rules
Phase 8 adds a local-only approved action export. This is not OAuth, not provider auth, and not mailbox automation. It turns human-approved SQLite audit events into an idempotent JSON bundle that n8n or a future provider bridge can consume later.
Example:
python3 -m botfucker.review_cli --db botfucker_review.sqlite3 approve sample-001 --actor you
python3 -m botfucker.review_cli --db botfucker_review.sqlite3 export-approved-actions > approved-actions.json
python3 -m botfucker.review_cli --db botfucker_review.sqlite3 export-approved-actions --since-audit-id audit-0001Export constraints:
- exports approved intent only (
approve_warningaudit events) - includes audit IDs/action IDs for downstream deduplication
- supports
--since-audit-idcursoring - omits message subject/snippet and provider credential material
- does not call Gmail, Microsoft, IMAP, SMTP, n8n, or any live provider from BotFucker core
- keeps browser UI actions local-only
Phase 9 adds an importable n8n dry-run bridge that consumes approved-actions.json, validates the botfucker.approved_actions.v1 schema, dedupes by audit_id, and logs would_execute records without provider side effects.
Files:
docs/n8n-approved-action-bridge.json— inactive n8n dry-run workflow starter.docs/n8n-approved-action-bridge.md— setup, input contract, dedupe notes, and live-bridge safety rules.
Starter loop:
python3 -m botfucker.review_cli --db botfucker_review.sqlite3 approve sample-001 --actor you
python3 -m botfucker.review_cli --db botfucker_review.sqlite3 export-approved-actions > approved-actions.jsonThen import docs/n8n-approved-action-bridge.json into n8n and point it at approved-actions.json:
export BOTFUCKER_APPROVED_ACTIONS="/path/to/approved-actions.json"
export BOTFUCKER_PROCESSED_AUDIT_IDS="audit-0001,audit-0002"Bridge constraints:
- starts inactive and dry-run/log-only
- validates
botfucker.approved_actions.v1 - dedupes by
audit_id - emits
provider_execution: not_performed - contains no Gmail, Microsoft, IMAP, SMTP, send-mail, move-mail, delete-mail, archive, or label mutation nodes in the starter
- keeps provider credentials in n8n and out of BotFucker core
Phase 10 adds an optional provider hook for model-assisted classification without handing trust to the model like a raccoon with a badge.
classify_message(..., llm_provider=provider) accepts a provider object with classify(payload) or a callable provider. The deterministic classifier still runs first as the baseline/fallback. Whitelisted and known-offender local safety states bypass the LLM entirely.
Provider contract:
{
"classification": "cold_outreach",
"confidence": 0.84,
"recommended_action": "warn_1",
"reasons": ["model saw sales intent", "model saw weak personalization"],
}Safety constraints:
- subject/body are marked as untrusted input in the provider payload
- body text is bounded before it reaches the provider
- raw headers are not sent to the provider
- output must use known classification/action values
- confidence must be between
0and1 - reasons are normalized and prefixed with
llm: - invalid output or provider failure falls back to deterministic classification
- no OAuth, provider credentials, send/move/delete/archive actions, or YOLO behavior are added by this feature
Phase 11 adds fail-closed guardrails for the legacy live automation path. This does not add OAuth, provider credentials, or new provider mutation features. It makes the existing --live --auto-approve path harder to trigger accidentally, because apparently “please don’t automate my inbox into a crater” needs code.
Guardrail primitive:
from botfucker.yolo_policy import YoloPolicy, evaluate_yolo_decisionLive mode now requires a YOLO policy before provider actions are allowed. The policy gates each provider action with:
enabled=True- exact confirmation phrase:
I ACCEPT BOTFUCKER YOLO RISK - emergency stop must be off
- provider action must be allowlisted
- classification must be allowlisted
- confidence must meet
min_confidence - daily action count must remain below
daily_action_limit - reply tone must be allowlisted
Environment variables for the legacy CLI path:
export BF_YOLO_ENABLED=true
export BF_YOLO_CONFIRMATION="I ACCEPT BOTFUCKER YOLO RISK"
export BF_YOLO_ALLOWED_ACTIONS="send_warning,write_blacklist,move_to_sales"
export BF_YOLO_ALLOWED_CLASSIFICATIONS="cold_outreach,ai_generated_pitch,crm_followup"
export BF_YOLO_MIN_CONFIDENCE=0.90
export BF_YOLO_DAILY_ACTION_LIMIT=10
export BF_YOLO_REPLY_TONE="firm_professional"
export BF_YOLO_EMERGENCY_STOP=falseIf any gate fails, BotFucker raises before the live provider action. Subtle? No. That is the point.
Phase 12 proved the n8n exports import and dry-run on Kent's real n8n target (n8n-vps, n8n 2.18.5) without attaching provider mutation credentials.
Artifacts:
scripts/validate_n8n_workflow_exports.py— static preflight for workflow exportssamples/approved-actions.sample.json— fake approved-action bundledocs/n8n-import-validation.md— exact import/dry-run/cleanup procedure and result
Actual result:
- both workflows imported inactive/manual
- approved-action bridge executed sample-only dry-run
- output stayed
provider_execution: not_performed - cleanup removed validation workflows and sample execution rows
Compatibility fixes shipped:
- workflow exports include explicit
idfields for n8n CLI import - file paths use
/home/node/.n8n-files, because n8n 2.18.5 blocks arbitrary local file paths - approved-action bridge parses
readWriteFilebinary JSON viagetBinaryDataBuffer
Phase 13 adds the reviewed path for promoting one approved provider action type from dry-run evidence toward a separately reviewed live n8n bridge. It is documentation and tests only: no OAuth, no provider credentials in BotFucker core, no live Gmail/Microsoft/IMAP/SMTP mutation nodes, and no change to local UI/provider behavior.
Artifact:
docs/reviewed-action-bridge-promotion-plan.md— operator/security/ops gate for one-action-at-a-time live bridge review.
The plan requires persistent processed-audit_id state, rollback and emergency-stop procedures, provider-specific sandbox/manual tests, and Rex/Gus review before any live mutation node is connected.
Phase 14 adds a durable bridge ledger scaffold for future reviewed provider bridges. It is not OAuth, not provider auth, and not live mailbox automation. The scaffold records approved-action audit_id state before provider mutation so a future bridge can fail closed on duplicates.
Artifacts:
botfucker/bridge_ledger.py— standard-library SQLite ledger keyed byaudit_id, withpending,processed,failed, androlled_backstates.docs/bridge-ledger-scaffold.md— operator/security notes for using the ledger before any provider mutation.
Safety constraints:
- effect scope is
bridge_ledger_state_only - validates
botfucker.approved_actions.v1,provider_action_export_only, andprovider_execution: not_performed - stores IDs/status only, not subject, snippet, body, headers, OAuth tokens, API keys, passwords, cookies, or private provider headers
- no OAuth, no provider credentials, and no live provider mutation nodes are added
- checked-in n8n workflows remain inactive/dry-run starters
Phase 17 adds an inactive/manual n8n scaffold for the selected Microsoft Outlook approve_warning draft path. It is still a scaffold: no OAuth setup, no checked-in auth material, no activation, and no mail delivery.
Artifacts:
docs/n8n-outlook-warning-draft-scaffold.json— inactive n8n workflow with manual trigger, fake approved-action input, emergency-stop/dedupe validation, a draft-only summary node, and a disabled/unconnected GraphcreateReplyplaceholder.docs/n8n-outlook-warning-draft-scaffold.md— operator checklist covering emergency stop, dedupe, rollback, and manual draft deletion.
Next step: sandbox import/rehearsal or operator validation of this scaffold. Do not jump from this artifact to live delivery or broad OAuth work.
Compile-check the script and package:
python3 -m py_compile outreach_filter.py botfucker/*.pyRun the unit tests. Tests use fake emails only and do not send mail:
python3 -m unittest discover -s tests -vRun a dry scan:
python3 outreach_filter.pyEmit dry-run/review output as JSON lines:
python3 outreach_filter.py --jsonRun live automation only after reviewing the dry-run output. --live must be paired with explicit --auto-approve and passing BF_YOLO_* guardrails before the tool can send replies, move messages, delete blacklisted messages, or update blacklist/history state:
python3 outreach_filter.py --live --auto-approveRun every 15 minutes with cron only after configuring YOLO guardrails and confirming the dry-run output. If BF_YOLO_EMERGENCY_STOP=true, live actions fail closed.
*/15 * * * * cd /path/to/BotFucker && /usr/bin/python3 outreach_filter.py --live --auto-approve >> outreach_filter.log 2>&1For Windows Task Scheduler, use:
Program: python
Arguments: C:\path\to\BotFucker\outreach_filter.py --live --auto-approve
The filter lists live in botfucker/classifier.py:
COLD_OUTREACH_PATTERNSAI_SIGNATURE_PATTERNS
Good filter ideas should be specific enough to catch bot-like outreach without catching real clients, coworkers, support threads, invoices, or personal messages.
Ideas are welcome. Useful contributions include:
- new cold outreach patterns
- better false-positive protections
- provider-specific IMAP folder handling
- safer dry-run reporting
- lightweight NLP experiments
- tests with anonymized sample emails
- documentation for more email providers
Please do not commit real emails, private contact lists, passwords, tokens, or production blacklist data.
Email rules vary by provider and jurisdiction. Test carefully, keep a whitelist, and make sure any automated reply behavior is appropriate for your use case.
