Campaign Management¶
Creating and managing phishing campaigns: email template design, SMTP configuration, landing page creation, site cloning, tracking pixel integration, and campaign metrics analysis.
Campaign Lifecycle¶
Every phishing campaign follows a defined state progression from creation through delivery:
stateDiagram-v2
[*] --> draft: Create campaign
draft --> sending: Send campaign
sending --> sent: All emails delivered
sending --> error: All sends failed Workflow overview:
- Create campaign -- Define name, sender identity, SMTP settings, and email template
- Configure SMTP -- Choose local relay SMTP or external SMTP server
- Select template -- Use a built-in template, import a custom EML file, or design from scratch
- Import targets -- Upload CSV with target email addresses, names, and companies
- Attach files -- Optionally attach payloads or documents to the campaign email
- Preview email -- Render the email with variable substitution to verify appearance
- Send campaign -- Dispatch emails asynchronously through the relay
- Monitor metrics -- Track opens, clicks, and credential captures in real time
Creating a Campaign¶
Create a new phishing campaign with POST /api/v1/phishing/campaigns.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Campaign name |
template_id | string | Yes | Built-in template ID (password-reset, invoice, it-support) or custom for imported EML |
from_email | string | Yes | Sender email address |
from_name | string | No | Sender display name |
smtp_host | string | No | External SMTP host (empty = use relay local SMTP) |
smtp_port | int | No | External SMTP port |
smtp_username | string | No | SMTP authentication username |
smtp_password | string | No | SMTP authentication password |
smtp_tls | bool | No | Enable STARTTLS |
landing_url | string | No | Base URL for tracking/landing page infrastructure |
delay_min_ms | int | No | Minimum delay between emails (ms) |
delay_max_ms | int | No | Maximum delay between emails (ms) |
bounce_to | string | No | Bounce address for NDRs |
Delay Settings
Use delay_min_ms and delay_max_ms to add random jitter between emails. This helps avoid rate limiting and makes the sending pattern appear more natural. A common range is 5000-15000 ms (5-15 seconds).
curl -s -X POST https://stentor.app/api/v1/phishing/campaigns \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Q1 Password Reset Campaign",
"template_id": "password-reset",
"from_email": "[email protected]",
"from_name": "IT Security Team",
"landing_url": "https://10.0.0.50:8443",
"delay_min_ms": 5000,
"delay_max_ms": 15000
}'
curl -s -X POST https://stentor.app/api/v1/phishing/campaigns \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Invoice Campaign",
"template_id": "invoice",
"from_email": "[email protected]",
"from_name": "Billing Department",
"smtp_host": "smtp.external-provider.com",
"smtp_port": 587,
"smtp_username": "[email protected]",
"smtp_password": "app-password-here",
"smtp_tls": true,
"landing_url": "https://10.0.0.50:8443",
"bounce_to": "[email protected]",
"delay_min_ms": 10000,
"delay_max_ms": 30000
}'
Response -- returns the full campaign object with a generated id and status: "draft".
SMTP Configuration¶
Stentor supports two SMTP delivery paths: the relay's embedded SMTP server (local) or an external SMTP relay.
Local Relay SMTP¶
When smtp_host is empty in the campaign configuration, emails route through the relay's embedded SMTP server. The relay runs an emersion/go-smtp server on the configured port.
Configuration:
- Port -- Set via the relay's
SMTP_PORTenvironment variable - Authentication -- No authentication required for local delivery (localhost only)
- STARTTLS -- Supported with auto-generated or provided TLS certificates
- Max message size -- 10 MB per email
- Max recipients -- 50 per message
TLS Certificates
When SMTP_ALLOW_INSECURE is not set, the relay auto-generates a TLS certificate for STARTTLS support using the same certificate infrastructure as the C2 listener.
External SMTP¶
When smtp_host is provided in the campaign, the relay connects to an external SMTP server for delivery.
Connection flow:
- TCP connection to
smtp_host:smtp_port - EHLO handshake
- STARTTLS upgrade (if server supports it)
- PLAIN authentication (if
smtp_usernameis provided) - MAIL FROM / RCPT TO / DATA sequence
Key behaviors:
- Bounce address -- When
bounce_tois set, it is used as the SMTP envelope sender (MAIL FROM), whilefrom_emailis used in the email From header. This separates NDR delivery from the visible sender identity. - TLS validation -- Certificate validation is skipped (
InsecureSkipVerify: true) for compatibility with varied SMTP servers and self-signed certificates.
Comparison¶
| Feature | Local Relay SMTP | External SMTP |
|---|---|---|
| Authentication | None (localhost) | PLAIN via STARTTLS |
| Setup | Zero config | Requires credentials |
| Sender control | Full (any address) | Limited by provider SPF/DKIM |
| Deliverability | Depends on relay IP reputation | Inherits provider reputation |
| TLS | Auto-generated cert | Server-provided cert |
| Bounce handling | Direct to relay | Configurable bounce address |
| Rate limiting | None (self-hosted) | Subject to provider limits |
Email Templates¶
Built-in Templates¶
Stentor ships with three ready-to-use email templates, rendered using the hermes email generation library for responsive, professional HTML emails.
password-reset -- Password Reset Request¶
Corporate IT-style password reset email. Simulates a security team requesting the user to reset their password.
- Default subject:
Action Required: Reset Your {{.CompanyName}} Password - Call to action: "Reset Password" button (red, links to
{{.ActionURL}}) - Urgency: Link expires in 24 hours
invoice -- Invoice Notification¶
Finance/billing notification email. Simulates an invoice or payment request.
- Default subject:
Invoice #{{.InvoiceNumber}} from {{.CompanyName}} - Call to action: "View Invoice" button (green, links to
{{.ActionURL}}) - Custom variables:
{{.InvoiceNumber}}(defaults toINV-YYYYMMDD),{{.Amount}}(defaults to$1,250.00)
it-support -- IT Support Verification¶
IT department account verification request. Creates urgency around suspicious account activity.
- Default subject:
{{.CompanyName}} IT: Verify Your Account - Call to action: "Verify Account" button (blue, links to
{{.ActionURL}}) - Urgency: Failure to verify within 48 hours may result in account suspension
Template Variables¶
All built-in templates support these variables, which are substituted at render time:
| Variable | Description | Example Value |
|---|---|---|
{{.RecipientName}} | Target's name | John Smith |
{{.RecipientEmail}} | Target's email | [email protected] |
{{.CompanyName}} | Company name for branding | Acme Corp |
{{.ActionURL}} | Phishing link (landing page) | https://10.0.0.50:8443/click/abc12345 |
{{.TrackingPixelURL}} | Invisible tracking pixel | https://10.0.0.50:8443/track/abc12345.gif |
Tracking Pixel Injection
All templates automatically inject a 1x1 transparent tracking pixel (<img> tag) before the closing </body> tag. This fires an email_opened event when the recipient's email client loads the image.
Custom Templates (EML Import)¶
Import a custom email template from an EML file using POST /api/v1/phishing/campaigns/:id/template/import.
The endpoint accepts either:
- Multipart form upload --
filefield with the EML file - JSON body --
eml_datafield containing the raw EML content as a string
EML parsing behavior:
- Parses RFC 5322 email format
- Extracts subject line (with MIME word decoding)
- Extracts HTML body and plain text body
- Supports
multipart/mixedandmultipart/alternativeMIME structures - Stores extracted content as the campaign's custom template
Custom template variables (substituted when sending):
| Variable | Description |
|---|---|
{{.ToName}} | Target's name |
{{.ToEmail}} | Target's email |
{{.FromName}} | Sender display name |
{{.FromEmail}} | Sender email address |
{{.CompanyName}} | Company name for branding |
{{.TrackingURL}} | Tracking pixel URL |
{{.LandingURL}} | Landing page URL |
curl -s -X POST "https://stentor.app/api/v1/phishing/campaigns/$CAMPAIGN_ID/template/import" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"eml_data": "From: [email protected]\r\nTo: {{.ToEmail}}\r\nSubject: Important Update from {{.CompanyName}}\r\nMIME-Version: 1.0\r\nContent-Type: text/html\r\n\r\n<html><body><h1>Hello {{.ToName}}</h1><p>Please review this update.</p><a href=\"{{.LandingURL}}\">Click here</a></body></html>"
}'
Response:
Email Preview¶
Preview a rendered email before sending with POST /api/v1/phishing/campaigns/:id/preview.
curl -s -X POST "https://stentor.app/api/v1/phishing/campaigns/$CAMPAIGN_ID/preview" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"target_email": "[email protected]",
"target_name": "John Smith",
"company_name": "Acme Corp"
}'
Response includes the rendered subject, html, and text versions of the email with all variables substituted.
Built-in Template Preview
Built-in templates (password-reset, invoice, it-support) are rendered server-side by the relay using the hermes library. Preview for these templates returns a placeholder indicating the template ID, since full rendering requires an active relay connection. Custom (EML-imported) templates render fully on the server side.
Target Management¶
Importing Targets¶
Import targets via CSV with POST /api/v1/phishing/campaigns/:id/targets/import.
CSV format: email,name,company
- Header row is auto-detected and skipped (if it contains "email" or "name")
- Each target receives a unique 8-character tracking ID (UUID prefix)
- Status starts as
pending - Rows with empty or invalid email addresses (missing
@) are silently skipped
curl -s -X POST "https://stentor.app/api/v1/phishing/campaigns/$CAMPAIGN_ID/targets/import" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"csv_data": "email,name,company\[email protected],John Smith,Target Corp\[email protected],Jane Doe,Target Corp\[email protected],Bob Wilson,Target Corp"
}'
Response:
Listing Targets¶
Retrieve all targets for a campaign with GET /api/v1/phishing/campaigns/:id/targets.
PhishingTarget fields:
| Field | Type | Description |
|---|---|---|
id | UUID | Target ID |
email | string | Target email address |
name | string | Target name |
company | string | Target company |
tracking_id | string | 8-character unique tracking ID |
status | string | pending, sent, opened, clicked, captured, error |
sent_at | timestamp | When email was delivered |
opened_at | timestamp | When tracking pixel was loaded |
clicked_at | timestamp | When phishing link was clicked |
captured_at | timestamp | When credentials were submitted |
error_message | string | Error details (if status is error) |
File Attachments¶
Attach files to campaign emails for payload delivery or social engineering.
Upload Attachment¶
POST /api/v1/phishing/campaigns/:id/attachments -- multipart/form-data with a file field.
curl -s -X POST "https://stentor.app/api/v1/phishing/campaigns/$CAMPAIGN_ID/attachments" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@/path/to/document.pdf"
Limits:
- Maximum 10 MB per file
- Maximum 10 MB total per campaign (across all attachments)
- Content type is auto-detected from file content (with fallback to the upload's Content-Type header)
List Attachments¶
GET /api/v1/phishing/campaigns/:id/attachments -- returns attachment metadata (filename, content type, size).
Delete Attachment¶
DELETE /api/v1/phishing/campaigns/:id/attachments/:attachmentId -- removes an attachment from the campaign.
Email construction: Attachments are included as multipart/mixed MIME parts in the email. Each attachment is Base64-encoded with Content-Disposition: attachment headers. The HTML email body becomes the first MIME part, with attachments following.
Site Cloning¶
Clone existing websites to create convincing landing pages for credential harvesting or payload delivery.
Clone a Website¶
POST /api/v1/sites -- clone a target website.
The site cloner uses Colly for web scraping and goquery for HTML manipulation:
- Fetches the target page HTML
- Collects all referenced assets (CSS, JS, images, fonts, favicon)
- Rewrites all URLs (href, src, action attributes) to serve from the listener path
- Optionally injects keylogger JavaScript or invisible iframe
Limits:
- Maximum total site size: 10 MB (HTML + all assets)
- Crawl depth: 1 (target page only, no deep crawling)
- Same-domain assets only (external CDN resources are left as-is)
Supported asset types:
| Category | Extensions |
|---|---|
| Stylesheets | .css |
| Scripts | .js |
| Images | .png, .jpg, .jpeg, .gif, .svg |
| Fonts | .woff, .woff2, .ttf, .eot |
| Icons | .ico |
Injection Options¶
Keylogger Injection¶
When enabled, a minimal JavaScript keylogger (~400 bytes minified) is injected before </body>:
- Captures
keypressevents on the page - Buffers keystrokes for 500 ms (debounced)
- Exfiltrates via Image pixel to
/logendpoint (CORS-friendly) - Tracking ID correlates keystrokes to the specific cloned site
- Self-contained IIFE to avoid global namespace pollution
iFrame Injection¶
When enabled, an invisible iframe is injected before </body>:
- Positioned off-screen (
top: -9999px,left: -9999px) - Zero opacity and zero dimensions
- Loads the specified payload URL silently
- Useful for drive-by payload delivery alongside a legitimate-looking cloned page
Site Management API¶
| Method | Endpoint | Description |
|---|---|---|
POST | /api/v1/sites | Clone a website |
GET | /api/v1/sites | List all cloned sites |
GET | /api/v1/sites/:id | Get cloned site details |
DELETE | /api/v1/sites/:id | Delete a cloned site |
GET | /api/v1/sites/:id/keystrokes | Get captured keystrokes for a site |
GET | /api/v1/keystrokes/captured | Get all captured keystrokes across all sites |
Landing Pages & Credential Capture¶
Landing pages are served by the relay's tracking server and capture credentials submitted by targets.
Built-in Landing Page Templates¶
Stentor includes three landing page templates that mimic common login portals:
microsoft -- Microsoft 365 Login¶
- Styling: Segoe UI font, blue accent (
#0067b8), clean white container - Fields: Email + Password
- Elements: "Can't access your account?" link for realism
google -- Google Accounts Login¶
- Styling: Google Sans / Roboto font, Material Design colors, rounded container with border
- Fields: Email + Password
- Elements: Colored Google logo, "Forgot email?" link, "Next" button
corporate -- Generic Corporate Login¶
- Styling: Gradient background (
#667eeato#764ba2), rounded card with drop shadow - Fields: Username + Password
- Customization: Configurable company name and logo URL
- Elements: "Forgot password?" link
URL Format¶
- GET renders the login form
- POST captures submitted credentials
Credential Capture Flow¶
sequenceDiagram
participant Target
participant Relay (Tracking)
participant Backend
participant Operator
Target->>Relay (Tracking): Click link in email
Note over Relay (Tracking): GET /click/{tracking_id}
Relay (Tracking)->>Backend: link_clicked event
Relay (Tracking)->>Target: 302 Redirect to landing page
Target->>Relay (Tracking): Load landing page
Note over Relay (Tracking): GET /landing/{page_id}/{tracking_id}
Relay (Tracking)->>Target: Render login form
Target->>Relay (Tracking): Submit credentials
Note over Relay (Tracking): POST /landing/{page_id}/{tracking_id}
Relay (Tracking)->>Backend: creds_captured event (username + password)
alt Redirect URL configured
Relay (Tracking)->>Target: 302 Redirect to configured URL
else No redirect
Relay (Tracking)->>Target: "Thank You" page
end
Backend->>Operator: Real-time UI update Landing Page Configuration¶
| Option | Description | Default |
|---|---|---|
| Default template | Template used when page_id not found | corporate |
| Company name | Brand name shown on corporate template | Your Company |
| Company logo | Logo URL for corporate template | (none) |
| Redirect URL | Where to send target after credential capture | https://www.google.com |
Username extraction: The credential capture handler checks multiple common form field names in order: username, email, user. This ensures credentials are captured regardless of which landing page template is used.
Drive-by Download Pages¶
Serve fake software update pages that trick targets into downloading payloads.
Templates¶
Three drive-by page templates are available:
| Template ID | Theme | Description |
|---|---|---|
chrome | Chrome Browser Update | Mimics a Chrome browser update notification |
windows | Windows Update | Mimics a Windows system update prompt |
adobe | Adobe Flash Update | Mimics an Adobe Flash/Reader update page |
URL Format¶
Query parameters:
| Parameter | Description | Default |
|---|---|---|
auto | Set to true for automatic download after page load | false |
filename | Override the displayed download filename | Original payload filename |
Workflow¶
- Host a payload -- Use the relay's
HostPayload()function to register a payload in memory (called via WebSocket command from the backend) - Generate the drive-by URL -- Construct the URL as
/update/{template}/{payload_id}with optional query parameters - Distribute the URL -- Include in a phishing email, embed in a cloned site iframe, or use as a redirect target
# Example drive-by URL with auto-download
https://10.0.0.50:8443/update/chrome/abc123-def456?auto=true&filename=ChromeSetup.exe
Payload Hosting
Payloads are stored in-memory on the relay. They persist only while the relay process is running. Restarting the relay clears all hosted payloads.
Tracking & Metrics¶
The relay's tracking server provides real-time visibility into campaign engagement.
Tracking Endpoints¶
| Endpoint | Method | Event Fired | Description |
|---|---|---|---|
/track/{tracking_id}.gif | GET | email_opened | Returns 1x1 transparent GIF, fires open event |
/click/{tracking_id} | GET | link_clicked | Fires click event, redirects to landing page |
/landing/{page_id}/{tracking_id} | POST | creds_captured | Captures username + password from form submission |
/payload/{payload_id} | GET | payload_downloaded | Serves hosted payload file |
Event Propagation¶
Events flow from the relay tracking server through the WebSocket connection to the backend:
Relay (Tracking HTTP) --> WebSocket Event --> Backend PhishingService.UpdateTargetEvent() --> Database Update --> UI Real-time Update
Each event includes:
- Tracking ID -- Correlates to a specific target
- Timestamp -- UTC time of the event
- User-Agent -- Target's browser identification
- IP -- Target's IP address (with X-Forwarded-For / X-Real-IP support)
Target Status Progression¶
stateDiagram-v2
[*] --> pending: Target imported
pending --> sent: Email delivered
sent --> opened: Tracking pixel loaded
opened --> clicked: Link clicked
clicked --> captured: Credentials submitted
pending --> error: Send failed Statuses progress forward only -- once a target reaches captured, earlier events (opens, clicks) are already recorded with timestamps.
CNA Script Events¶
The phishing service dispatches Cobalt Strike-compatible CNA events at each stage of the campaign lifecycle:
| Event | Arguments | Description |
|---|---|---|
sendmail_start | $1 campaign_id, $2 target_count, $3 attachment_path, $4 bounce_to, $5 smtp_server, $6 subject, $7 template_id | Campaign begins sending |
sendmail_pre | $1 campaign_id, $2 target_email | Before each email is sent |
sendmail_post | $1 campaign_id, $2 target_email, $3 status, $4 server_message | After successful email delivery |
sendmail_error | $1 campaign_id, $2 target_email, $3 error_message | Email delivery failed |
sendmail_done | $1 campaign_id | Campaign sending complete |
CNA Hook Example
Sending a Campaign¶
Launch email delivery with POST /api/v1/phishing/campaigns/:id/send.
curl -s -X POST "https://stentor.app/api/v1/phishing/campaigns/$CAMPAIGN_ID/send" \
-H "Authorization: Bearer $TOKEN"
Sending behavior:
- Campaign status changes to
sending - Emails are dispatched asynchronously via the relay WebSocket connection
- The backend selects the first connected relay for delivery
- Random delay between emails (configurable via
delay_min_ms/delay_max_ms) - Each target's status updates individually as emails are sent (
sentorerror) - Final campaign status:
sent(at least one succeeded) orerror(all failed)
Relay Required
At least one relay must be connected via WebSocket. If no relays are available, the campaign immediately transitions to error status with the message "no connected relays".
Complete End-to-End Example¶
Full workflow from campaign creation through monitoring:
# 1. Create campaign
CAMPAIGN_ID=$(curl -s -X POST https://stentor.app/api/v1/phishing/campaigns \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Q1 Security Awareness Test",
"template_id": "password-reset",
"from_email": "[email protected]",
"from_name": "IT Security",
"landing_url": "https://10.0.0.50:8443",
"delay_min_ms": 5000,
"delay_max_ms": 15000
}' | jq -r '.id')
echo "Campaign ID: $CAMPAIGN_ID"
# 2. Import targets
curl -s -X POST "https://stentor.app/api/v1/phishing/campaigns/$CAMPAIGN_ID/targets/import" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"csv_data": "email,name,company\[email protected],John Smith,Target Corp\[email protected],Jane Doe,Target Corp\[email protected],Bob Wilson,Target Corp"
}'
# 3. Preview email (optional)
curl -s -X POST "https://stentor.app/api/v1/phishing/campaigns/$CAMPAIGN_ID/preview" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"target_email": "[email protected]",
"target_name": "John Smith",
"company_name": "Target Corp"
}' | jq '.subject, .html[:200]'
# 4. Send campaign
curl -s -X POST "https://stentor.app/api/v1/phishing/campaigns/$CAMPAIGN_ID/send" \
-H "Authorization: Bearer $TOKEN"
# 5. Monitor campaign status
curl -s "https://stentor.app/api/v1/phishing/campaigns/$CAMPAIGN_ID" \
-H "Authorization: Bearer $TOKEN" | jq '{status, total_targets, sent_count}'
# 6. Check target engagement
curl -s "https://stentor.app/api/v1/phishing/campaigns/$CAMPAIGN_ID/targets" \
-H "Authorization: Bearer $TOKEN" | jq '.[] | {email, status, opened_at, clicked_at, captured_at}'
Cross-references¶
- REST API Reference -- Full endpoint documentation for phishing and site cloning APIs
- Relay Management -- Relay SMTP infrastructure setup and configuration
- Hooks & Events -- CNA sendmail event hooks for scripted automation