Gradually raise the temperature of phishing campaign sender emails/domains!
A domain warming tool that progressively ramps email volume using a three-phase approach: seed (send between accounts you control), transition (mix of seed and real targets), and target (full outbound). Generates OPSEC-aware headers with X-Mailer-consistent Message-IDs, date jitter, optional thread simulation, and multipart HTML+plaintext bodies.
Designed to run fire-and-forget on a GCP/AWS VM for the duration of an engagement's warm-up period.
-
Provision a VM (GCP or AWS) and ensure the sending domain(s) have proper SPF, DKIM, and DMARC records.
-
Install swaks
apt install perl -y curl -O https://jetmore.org/john/code/swaks/files/swaks-20240103.0/swaks chmod 755 ./swaks
-
Configure your files:
- config.json — Campaign settings (see Configuration)
- send.json — Sender credentials
- receive.json — Recipients with
typefield (seedortarget) - messages.json — Message templates with optional HTML and thread simulation
config.json controls the campaign behavior:
{
"campaign_name": "acme-q3",
"campaign_days": 10,
"business_hours": {"start": "08:30", "end": "17:00", "timezone_offset": -5},
"skip_weekends": true,
"volume_curve": "logarithmic",
"phase_schedule": {"seed_only_days": 3, "transition_days": 2},
"per_sender_daily_max": 5,
"delay_profile": {"min": 30, "max": 300, "distribution": "weighted"},
"provider": "mailgun",
"smtp_server": "smtp.mailgun.org",
"smtp_port": 587,
"notifications": {"webhook_url": null},
"credentials_file": "send.json",
"recipients_file": "receive.json",
"messages_file": "messages.json"
}| Field | Description |
|---|---|
campaign_days |
Total campaign length (default: 10) |
volume_curve |
"logarithmic" (1,2,3,5,8,12,15,20,25,30), "linear" (day N = N), or a custom array [1,2,4,8,16] |
phase_schedule |
Days for seed-only and transition phases before full target sending |
per_sender_daily_max |
Max emails per sender credential per day (prevents single-account spikes) |
delay_profile |
Inter-email delay range and distribution ("weighted" for human-like, "uniform" for flat random) |
skip_weekends |
No sends on Saturday/Sunday |
business_hours |
Sending window in local time (uses timezone_offset from UTC) |
notifications.webhook_url |
Optional Slack/Discord webhook for daily status updates |
[
{"email": "seed1@yourdomain.com", "type": "seed"},
{"email": "target@client.com", "type": "target"}
]- seed: Mailboxes you control. Used in phase 1 to build sender reputation with guaranteed delivery.
- target: Actual engagement recipients. Used in phase 2-3 after reputation is established.
[
{
"subject": "Follow Up",
"body": "Following up on our conversation...",
"body_html": "<p>Following up on our conversation...</p>",
"thread": true
}
]body_html(optional): HTML variant for multipart/alternative emails. Most real business email includes both.thread(optional): Whentrue, 50% chance of addingIn-Reply-To/Referencesheaders andRe:prefix, simulating a reply chain.
# Preview what would be sent today (no SMTP)
python3 email_sender.py --dry-run# Start long-running daemon — sleeps between business-hours windows
python3 email_sender.py --run-campaign
# Run in background with nohup
nohup python3 email_sender.py --run-campaign > daemon.log 2>&1 &
# Or install as a system service
python3 email_sender.py --install-service # generates systemd unit or launchd plist# Install cron job (with proper cd, logging, weekend skip)
python3 email_sender.py --setup-cron
# Remove cron entry
python3 email_sender.py --remove-cron# Check campaign progress
python3 email_sender.py --status
# Force send N emails (ignores schedule/phase)
python3 email_sender.py --force-send 5
# Reset campaign to start fresh
python3 email_sender.py --resetThe three-phase approach builds sender reputation before touching the client's mail infrastructure:
Phase 1: SEED (days 1-3)
Send to mailboxes you control → guaranteed delivery, no bounces
Volume: 1, 2, 3 emails/day
Phase 2: TRANSITION (days 4-5)
Mix of seed and target recipients, ratio shifts toward targets
Day 4: ~50/50 seed/target
Day 5: ~80% target
Volume: 5, 8 emails/day
Phase 3: TARGET (days 6+)
Full outbound to engagement targets
Volume: 12, 15, 20, 25, 30 emails/day
- X-Mailer-consistent Message-IDs: Each email picks a mailer profile (Outlook, Thunderbird, Apple Mail) and generates a Message-ID matching that client's format
- Date header jitter: Timestamps vary by +/- 3 minutes with timezone matching
- Thread simulation: Optional
In-Reply-To/Referencesheaders on messages marked"thread": true - Multipart bodies: HTML + plaintext (text-only from "Outlook 16.0" is a detection signal)
- Business-hours only: Sends within configured window, skips weekends
- Weighted delays: Beta-distributed inter-email timing with occasional bursts and pauses (not uniform random)
- Per-sender rate limiting: No single credential sends more than
per_sender_daily_maxper day - Bounce blacklisting: Recipients that bounce (5xx) are permanently excluded
- Python 3.6+ (stdlib only, no pip dependencies)
- swaks email tool
- Valid SMTP credentials (Mailgun, SendGrid, SES, or custom)
Keep send.json and config.json secure. Never commit real credentials to version control.
The tool sends via swaks, so adding a provider is just a matter of the right SMTP server and auth flags. Set smtp_server and smtp_port in config.json:
| Provider | smtp_server |
smtp_port |
Notes |
|---|---|---|---|
| Mailgun | smtp.mailgun.org |
587 | Default. Use domain-level SMTP credentials. |
| SendGrid | smtp.sendgrid.net |
587 | Username is always apikey, password is your API key. |
| Amazon SES | email-smtp.{region}.amazonaws.com |
587 | Generate SMTP credentials in SES console. |
| Custom/Postfix | Your server IP/hostname | 25/587 | May need --tls removed from swaks for port 25. |
For providers that need additional swaks flags (e.g., different TLS handling), submit a PR adding a provider profile to the send_email function.
