Phishing Simulation
Run internal phishing-awareness campaigns against your own organization's staff and measure who opens the email, clicks the tracked link, submits credentials on a fake login page, or correctly reports it.
/phishing-simHow it works
OverviewA campaign is an HTML email template plus a target filter and a schedule. When you launch it, recipients are resolved from your organization's own active user accounts — the resolver in lib/phishing/launch.ts only ever reads real User rows that belong to your org via membership, and never accepts an email address or recipient list from the caller. You cannot target external addresses.
Each resolved recipient is given a unique random token. That token is embedded in the email's tracking pixel, its tracked link, and its "report" link, so every recipient's individual actions are tracked separately. As those actions come in, the campaign's results roll up into a funnel:
| Stage | What it means | How it is recorded |
|---|---|---|
| Sent | The simulated email was delivered to the recipient's mailbox. | Set when the send succeeds; a failed send is shown as Failed instead. |
| Opened | The recipient opened the email. | A hidden 1×1 tracking pixel loads and marks openedAt. |
| Clicked | The recipient clicked the tracked link in the email. | Marks clickedAt and raises a Medium-severity alert. |
| Entered Creds | The recipient typed a username/password into the fake login page and submitted. | Marks credsEnteredAt and raises a High-severity alert. |
| Reported | The recipient recognized the email and used the "report" link — the desired outcome. | Marks reportedAt; no alert is created (reporting is a positive signal). |
The simulated email is always sent from your organization's own verified sending address. The "sender display name" only changes the friendly name recipients see (for example, "IT Support Team") — it cannot be used to spoof a real external domain, because doing so would break SPF/DKIM/DMARC on the deployment's real mail domain.
Building and running a campaign
How to useCreate a campaign (draft)
From the list view, choose New Campaign and fill in the form:
- Campaign name — an internal label for the test.
- Sender display name — the "from" name recipients see (defaults to IT Support Team). The underlying address stays your org's verified sending domain.
- Email subject and Email body (HTML) — the body is raw HTML. Insert the placeholder
{{link}}wherever you want the tracked link; it is rewritten to the per-recipient tracking URL at send time. - Target — either All active users in org or a Specific department. Recipients are always resolved from your org's active users at launch time.
- Send — Immediately on launch, or At a scheduled time (pick a date/time).
Use Preview Email to see exactly what a recipient would receive; the preview's link points at a harmless, token-less copy of the fake landing page, and nothing is sent or tracked from it. Choosing Create Draft saves the campaign in draft status — no email goes out yet.
Launch, monitor, and control
Open a campaign to see its funnel tiles and a per-recipient results table (User, Sent, Opened, Clicked, Entered Creds, Reported). The available actions depend on status:
- Launch Campaign (draft only) — resolves recipients from your org, creates a tracked row per recipient, and moves the campaign to
scheduled. If the send mode is "immediately", the first batch goes out right away; a scheduled campaign waits for its start time. - Pause (while scheduled or sending) — the background sender skips paused campaigns, so no further email goes out until you resume.
- Resume (while paused) — returns the campaign to
scheduledso the sender picks it back up. - Preview — re-open the email preview for the saved template.
- Export CSV — download every recipient's outcome as a CSV.
- Delete — permanently removes the campaign and all its recipient tracking data (a confirmation prompt warns this can't be undone).
Creating, launching, pausing, resuming, editing, and deleting a campaign all require the manage:phishing_campaigns permission, because launching sends real email to real staff. Viewing the campaign list and results only requires access to the module.
Tracking, alerts, and recipient safety
OverviewThe tracked link takes a recipient to a fake sign-in page at /phishing-sim/landing/ that renders a plain "Sign in to continue" form. If they submit it — or use the report link — the page reveals a training message explaining that this was a simulated phishing test and that nothing they typed was checked or stored.
Two recipient actions are treated as failures and create a real Alert (source phishing-simulation), so they surface in the SIEM/incident workflow and can trigger SOAR playbooks rather than only appearing on the campaign's results tab:
- Clicking the tracked link → Medium-severity alert, tagged
phishing-sim/clicked. - Entering credentials on the fake page → High-severity alert, tagged
phishing-sim/credentials-entered.
Several safety properties are enforced in the code itself:
- Submitted usernames and passwords are read for presence only and immediately discarded — they are never logged or persisted anywhere.
- The click endpoint always redirects to a fixed internal landing path, never to a campaign- or attacker-controlled URL, so it can't become an open redirect.
- The open, click, and report endpoints are single-purpose and token-gated — a token can only affect its own recipient row.
- Each tracked event is recorded once; re-opening or re-clicking does not double-count.
API reference
ReferenceCampaign endpoints (session, org-scoped)
| Method & path | Purpose |
|---|---|
GET /api/phishing/campaigns | List your org's campaigns with recipient counts. |
POST /api/phishing/campaigns | Create a draft. Requires name, template.subject, template.htmlBody; also accepts template.fromName, template.reportLinkLabel, targetFilter, and schedule. |
GET /api/phishing/campaigns/[id] | Fetch a single campaign. |
PATCH /api/phishing/campaigns/[id] | Edit name/template/target/schedule. Only draft campaigns can be edited. (API only — no edit form in the UI.) |
DELETE /api/phishing/campaigns/[id] | Delete the campaign and its recipient rows. |
POST /api/phishing/campaigns/[id]/launch | Resolve recipients and start sending. Returns the campaign plus recipientCount. |
POST /api/phishing/campaigns/[id]/pause | Pause a scheduled or in-progress campaign. |
POST /api/phishing/campaigns/[id]/resume | Resume a paused campaign. |
GET /api/phishing/campaigns/[id]/results | Campaign, funnel totals, and per-recipient rows (backs the detail view). |
GET /api/phishing/campaigns/[id]/export | CSV of recipient outcomes. |
GET /api/phishing/risk/[userId] | Cross-campaign click/report history and summary for one user. (API only.) |
Public tracking endpoints (token-gated, unauthenticated)
| Method & path | Effect |
|---|---|
GET /api/phishing/track/open/[token] | Returns a 1×1 pixel and marks the recipient as opened. |
GET /api/phishing/track/click/[token] | Marks clicked, raises a Medium alert, redirects to the fake landing page. |
POST /api/phishing/track/submit/[token] | The fake login form posts here; marks creds-entered, raises a High alert, discards the submitted values. |
GET / POST /api/phishing/track/report/[token] | Marks the email as reported (no alert). |
CSV export columns
name, email, department, emailSentAt, emailError, openedAt, clickedAt, credsEnteredAt, reportedAt. The file is named phishing-.
Create and launch a campaign
curl -X POST https://app.guardfoxsecurity.com/api/phishing/campaigns \
-H "Content-Type: application/json" \
--cookie "next-auth.session-token=" \
-d '{
"name": "Q3 Password Reset Awareness Test",
"template": {
"subject": "Action required: verify your account",
"htmlBody": "Please verify your account: Verify
",
"fromName": "IT Support Team",
"reportLinkLabel": "Report this email"
},
"targetFilter": { "active": true, "department": "Finance" },
"schedule": { "mode": "now" }
}'
# then launch the returned draft by id
curl -X POST https://app.guardfoxsecurity.com/api/phishing/campaigns//launch \
--cookie "next-auth.session-token="