Skip to main content
Targets in Phishmonger represent individual recipients of phishing emails with associated metadata. This guide covers adding, managing, and organizing target lists.

Target Structure

Each target contains:
FieldTypeDescriptionRequired
target_idstringUnique randomly generated IDAuto-generated
addressstringEmail addressYes
campaignstringAssociated campaign nameYes
first_namestringFirst name for personalizationNo
last_namestringLast name for personalizationNo
positionstringJob title for personalizationNo
customstringCustom field for any additional dataNo
phishedinteger0 = not sent, 1 = email sentAuto-managed

Target ID

Purpose: Unique identifier for tracking Format: 6-character alphanumeric string (e.g., a7k2m9) Generation: Automatically generated using:
Math.random().toString(36).slice(2).substr(0, 6)
Usage: Appended to phishing links as tracking parameter Example:
https://payload-domain.com/?id=a7k2m9

Phished Status

Values:
  • 0: Target has not been sent an email yet
  • 1: Email has been sent to this target
Behavior:
  • Initially set to 0 when target is created
  • Automatically set to 1 after email is successfully sent
  • Campaign only sends to targets where phished = 0
  • Can be manually reset to retry sending

Adding Targets

Via Web Interface

1

Access Campaign

From admin interface, click campaign name
2

Navigate to Edit Targets

Click “Edit Targets” or similar option
3

Add Individual Target

Fill in target form:
  • Email address (required)
  • First name
  • Last name
  • Position
  • Custom field
4

Click Add Target

Target is added to campaign

Via API

Add target using the API:
curl -X POST https://yourdomain.com/create_target \
  -H "Cookie: admin_cookie=YOUR_COOKIE_VALUE" \
  -H "Content-Type: application/json" \
  -d '{
    "campaign": "campaign_name",
    "address": "john.smith@target.com",
    "first_name": "John",
    "last_name": "Smith",
    "position": "IT Administrator",
    "custom": "+1-555-0123"
  }'
Response:
{
  "target_id": "a7k2m9",
  "address": "john.smith@target.com",
  "campaign": "campaign_name",
  "first_name": "John",
  "last_name": "Smith",
  "position": "IT Administrator",
  "custom": "+1-555-0123",
  "phished": 0
}

Via Database

Add targets directly to database:
sqlite3 db/aquarium.db
INSERT INTO targets VALUES (
  'a7k2m9',                    -- target_id
  'john.smith@target.com',     -- address
  'campaign_name',             -- campaign
  'John',                      -- first_name
  'Smith',                     -- last_name
  'IT Administrator',          -- position
  '+1-555-0123',              -- custom
  0                            -- phished
);

Bulk Importing Targets

CSV Format

Create a CSV file with target information:
address,first_name,last_name,position,custom
john.smith@target.com,John,Smith,IT Administrator,+1-555-0123
jane.doe@target.com,Jane,Doe,HR Manager,+1-555-0124
bob.jones@target.com,Bob,Jones,CEO,+1-555-0125

Import Script

Create a script to import CSV:
const fs = require('fs');
const Database = require('better-sqlite3');

const db = new Database('./db/aquarium.db');
const campaign = 'campaign_name';

// Read CSV file
const csv = fs.readFileSync('targets.csv', 'utf8');
const lines = csv.split('\n');
const headers = lines[0].split(',');

// Insert each target
const insert = db.prepare(`
  INSERT INTO targets VALUES (?, ?, ?, ?, ?, ?, ?, 0)
`);

for (let i = 1; i < lines.length; i++) {
  if (!lines[i].trim()) continue;

  const values = lines[i].split(',');
  const targetId = Math.random().toString(36).slice(2).substr(0, 6);

  insert.run(
    targetId,
    values[0], // address
    campaign,
    values[1], // first_name
    values[2], // last_name
    values[3], // position
    values[4]  // custom
  );

  console.log(`Added: ${values[0]}`);
}

console.log('Import complete!');
Run the script:
node import_targets.js

SQL Import

Import directly via SQL:
sqlite3 db/aquarium.db
-- Create temporary table for CSV data
CREATE TEMP TABLE temp_targets (
  address TEXT,
  first_name TEXT,
  last_name TEXT,
  position TEXT,
  custom TEXT
);

-- Import CSV
.mode csv
.import targets.csv temp_targets

-- Insert into targets table with generated IDs
INSERT INTO targets
SELECT
  lower(hex(randomblob(3))),  -- target_id (6 char hex)
  address,
  'campaign_name',             -- Replace with actual campaign name
  first_name,
  last_name,
  position,
  custom,
  0                            -- phished
FROM temp_targets
WHERE address IS NOT NULL AND address != 'address';  -- Skip header row

-- Cleanup
DROP TABLE temp_targets;

Viewing Targets

Via Web Interface

1

Access Campaign

Click campaign name from admin interface
2

View Targets List

Target list displays all added targets with:
  • Email address
  • Name
  • Position
  • Phished status
  • Actions (view details, delete)

Via API

Retrieve campaign targets:
curl -X GET "https://yourdomain.com/get_targets?campaign=campaign_name" \
  -H "Cookie: admin_cookie=YOUR_COOKIE_VALUE"
Response:
[
  {
    "target_id": "a7k2m9",
    "address": "john.smith@target.com",
    "campaign": "campaign_name",
    "first_name": "John",
    "last_name": "Smith",
    "position": "IT Administrator",
    "custom": "+1-555-0123",
    "phished": 0
  },
  {
    "target_id": "b8n3p1",
    "address": "jane.doe@target.com",
    "campaign": "campaign_name",
    "first_name": "Jane",
    "last_name": "Doe",
    "position": "HR Manager",
    "custom": "+1-555-0124",
    "phished": 1
  }
]

Via Database

Query targets directly:
-- All targets for campaign
SELECT * FROM targets WHERE campaign = 'campaign_name';

-- Only targets not yet sent
SELECT * FROM targets WHERE campaign = 'campaign_name' AND phished = 0;

-- Only targets already sent
SELECT * FROM targets WHERE campaign = 'campaign_name' AND phished = 1;

-- Count by status
SELECT
  COUNT(*) as total,
  SUM(phished) as sent,
  COUNT(*) - SUM(phished) as remaining
FROM targets
WHERE campaign = 'campaign_name';

Editing Targets

Update Phished Status

Reset phished status to resend emails: Via API:
curl -X PUT https://yourdomain.com/update_phished_status \
  -H "Cookie: admin_cookie=YOUR_COOKIE_VALUE" \
  -H "Content-Type: application/json" \
  -d '{
    "campaign": "campaign_name",
    "address": "john.smith@target.com",
    "phished": 0
  }'
Via Database:
-- Reset specific target
UPDATE targets
SET phished = 0
WHERE campaign = 'campaign_name' AND address = 'john.smith@target.com';

-- Reset all targets in campaign
UPDATE targets
SET phished = 0
WHERE campaign = 'campaign_name';

-- Reset only failed targets (with ERROR events)
UPDATE targets
SET phished = 0
WHERE target_id IN (
  SELECT DISTINCT target
  FROM events
  WHERE campaign = 'campaign_name' AND event_type = 'ERROR'
);

Update Target Information

Modify target details:
-- Update name
UPDATE targets
SET first_name = 'Jonathan', last_name = 'Smith'
WHERE target_id = 'a7k2m9';

-- Update position
UPDATE targets
SET position = 'Senior IT Administrator'
WHERE target_id = 'a7k2m9';

-- Update custom field
UPDATE targets
SET custom = '+1-555-9999'
WHERE target_id = 'a7k2m9';
Campaign Must Be Stopped: Do not edit targets while a campaign is actively sending. Stop the campaign first.

Deleting Targets

Via API

Remove individual target:
curl -X DELETE https://yourdomain.com/delete_target \
  -H "Cookie: admin_cookie=YOUR_COOKIE_VALUE" \
  -H "Content-Type: application/json" \
  -d '{
    "campaign": "campaign_name",
    "address": "john.smith@target.com"
  }'
Behavior: Deletes target AND all associated events

Via Database

Delete targets directly:
-- Delete specific target
DELETE FROM targets
WHERE campaign = 'campaign_name' AND address = 'john.smith@target.com';

-- Delete all targets for campaign
DELETE FROM targets WHERE campaign = 'campaign_name';

-- Delete targets that have already been sent
DELETE FROM targets WHERE campaign = 'campaign_name' AND phished = 1;

-- Delete targets with ERROR events (failed delivery)
DELETE FROM targets
WHERE target_id IN (
  SELECT DISTINCT target
  FROM events
  WHERE campaign = 'campaign_name' AND event_type = 'ERROR'
);
Associated Events: When deleting via API, associated events are automatically deleted. When deleting via database, manually delete events:
-- Delete events for specific target
DELETE FROM events WHERE target = 'a7k2m9';

-- Delete events for all targets in campaign
DELETE FROM events WHERE campaign = 'campaign_name';

Target Personalization

String Substitutions

Phishmonger replaces placeholders in emails with target-specific values:
PlaceholderTarget FieldExample
SuppliedFirstNamefirst_nameJohn
SuppliedLastNamelast_nameSmith
SuppliedPositionpositionIT Administrator
SuppliedCustomReplacementcustom+1-555-0123
SuppliedToAddressaddressjohn.smith@target.com
SuppliedPhishingLink(computed)https://payload-domain.com/?id=a7k2m9

Example Email with Substitutions

Template:
<p>Hello SuppliedFirstName SuppliedLastName,</p>

<p>As SuppliedPosition at the company, you are required to complete
annual security training.</p>

<p>Your employee ID: SuppliedCustomReplacement</p>

<p>Please confirm your email address (SuppliedToAddress) and complete
the training:</p>

<p><a href="SuppliedPhishingLink">Start Training</a></p>
After Substitution:
<p>Hello John Smith,</p>

<p>As IT Administrator at the company, you are required to complete
annual security training.</p>

<p>Your employee ID: +1-555-0123</p>

<p>Please confirm your email address (john.smith@target.com) and complete
the training:</p>

<p><a href="https://payload-domain.com/?id=a7k2m9">Start Training</a></p>

Best Practices

Required Fields:
  • Always provide first_name if using SuppliedFirstName
  • Emails look less legitimate with blank substitutions
Fallback Values: If target data is missing, consider providing defaults:
-- Add default values for missing names
UPDATE targets
SET first_name = 'User'
WHERE campaign = 'campaign_name' AND (first_name IS NULL OR first_name = '');
Custom Field Usage:
  • Phone numbers
  • Employee IDs
  • Department names
  • Office locations
  • Account numbers
  • Manager names

Target List Management

Deduplication

Remove duplicate email addresses:
-- Find duplicates
SELECT address, COUNT(*)
FROM targets
WHERE campaign = 'campaign_name'
GROUP BY address
HAVING COUNT(*) > 1;

-- Keep first occurrence, delete duplicates
DELETE FROM targets
WHERE rowid NOT IN (
  SELECT MIN(rowid)
  FROM targets
  WHERE campaign = 'campaign_name'
  GROUP BY address
);

Validation

Validate email addresses:
-- Find invalid email formats (basic check)
SELECT * FROM targets
WHERE campaign = 'campaign_name'
AND address NOT LIKE '%_@%_._%';

-- Find missing @ symbol
SELECT * FROM targets
WHERE campaign = 'campaign_name'
AND address NOT LIKE '%@%';
External Validation: Export and validate with email verification tools
# Export email addresses
sqlite3 db/aquarium.db "SELECT address FROM targets WHERE campaign = 'campaign_name';" > emails.txt

Categorization

Organize targets by attributes:
-- Group by domain
SELECT
  SUBSTR(address, INSTR(address, '@') + 1) as domain,
  COUNT(*) as count
FROM targets
WHERE campaign = 'campaign_name'
GROUP BY domain;

-- Group by position
SELECT position, COUNT(*) as count
FROM targets
WHERE campaign = 'campaign_name'
GROUP BY position
ORDER BY count DESC;

Target Statistics

Campaign Metrics

-- Total targets
SELECT COUNT(*) as total_targets
FROM targets
WHERE campaign = 'campaign_name';

-- Emails sent vs remaining
SELECT
  COUNT(*) as total,
  SUM(phished) as sent,
  COUNT(*) - SUM(phished) as remaining,
  (SUM(phished) * 100.0 / COUNT(*)) as percent_complete
FROM targets
WHERE campaign = 'campaign_name';

-- Targets with clicks
SELECT COUNT(DISTINCT t.target_id) as targets_clicked
FROM targets t
JOIN events e ON t.target_id = e.target
WHERE t.campaign = 'campaign_name'
AND e.event_type = 'CLICK';

-- Targets with submissions
SELECT COUNT(DISTINCT t.target_id) as targets_submitted
FROM targets t
JOIN events e ON t.target_id = e.target
WHERE t.campaign = 'campaign_name'
AND e.event_type = 'POST_DATA';

Target Details

View specific target and their events:
-- Target information
SELECT * FROM targets WHERE target_id = 'a7k2m9';

-- Target events
SELECT event_timestamp, event_type, event_data
FROM events
WHERE target = 'a7k2m9'
ORDER BY event_timestamp ASC;

-- Target timeline
SELECT
  datetime(event_timestamp / 1000, 'unixepoch') as timestamp,
  event_type,
  event_ip,
  event_data
FROM events
WHERE target = 'a7k2m9'
ORDER BY event_timestamp ASC;

Exporting Targets

Export to CSV

sqlite3 db/aquarium.db
.mode csv
.output targets_export.csv
SELECT * FROM targets WHERE campaign = 'campaign_name';
.output stdout

Export Email List Only

sqlite3 db/aquarium.db "SELECT address FROM targets WHERE campaign = 'campaign_name';" > email_list.txt

Export with Status

sqlite3 db/aquarium.db <<EOF
.mode csv
.output campaign_results.csv
SELECT
  t.address,
  t.first_name,
  t.last_name,
  t.position,
  t.phished,
  CASE WHEN e_click.target IS NOT NULL THEN 1 ELSE 0 END as clicked,
  CASE WHEN e_submit.target IS NOT NULL THEN 1 ELSE 0 END as submitted
FROM targets t
LEFT JOIN (SELECT DISTINCT target FROM events WHERE event_type = 'CLICK') e_click
  ON t.target_id = e_click.target
LEFT JOIN (SELECT DISTINCT target FROM events WHERE event_type = 'POST_DATA') e_submit
  ON t.target_id = e_submit.target
WHERE t.campaign = 'campaign_name';
.output stdout
EOF

Target Privacy and Security

Sensitive Data

What to Store:
  • Email addresses (required)
  • First/last names (for personalization)
  • Job titles (for targeting)
What NOT to Store:
  • Passwords
  • Social Security Numbers
  • Sensitive personal information
  • Financial data
Custom Field Usage:
  • Use for campaign-specific tracking only
  • Avoid storing data not needed for phishing
  • Clear custom fields after campaign completion

Data Retention

After campaign completion:
-- Delete target data after reporting
DELETE FROM targets WHERE campaign = 'completed_campaign_name';

-- Or anonymize targets
UPDATE targets
SET
  first_name = 'REDACTED',
  last_name = 'REDACTED',
  position = 'REDACTED',
  custom = 'REDACTED'
WHERE campaign = 'completed_campaign_name';

Database Security

Protect target data:
# Restrict database file permissions
chmod 600 db/aquarium.db

# Encrypt database at rest (example with SQLCipher)
# Note: Requires code changes to use encrypted database

Troubleshooting Targets

Targets Not Receiving Emails

Check target exists:
SELECT * FROM targets WHERE campaign = 'campaign_name' AND phished = 0;
Verify email format:
SELECT * FROM targets
WHERE campaign = 'campaign_name'
AND (address NOT LIKE '%@%' OR address LIKE '% %');
Check for ERROR events:
SELECT t.address, e.event_data
FROM targets t
JOIN events e ON t.target_id = e.target
WHERE t.campaign = 'campaign_name'
AND e.event_type = 'ERROR';

Duplicate Target IDs

If target IDs collide (very rare with 6-character random IDs):
-- Find duplicate target_ids
SELECT target_id, COUNT(*)
FROM targets
GROUP BY target_id
HAVING COUNT(*) > 1;

-- Generate new IDs for duplicates
UPDATE targets
SET target_id = lower(hex(randomblob(3)))
WHERE target_id IN (
  SELECT target_id FROM targets GROUP BY target_id HAVING COUNT(*) > 1
)
AND rowid NOT IN (
  SELECT MIN(rowid) FROM targets GROUP BY target_id HAVING COUNT(*) > 1
);

Missing Target Data

If string substitutions appear blank in emails:
-- Find targets with missing data
SELECT target_id, address
FROM targets
WHERE campaign = 'campaign_name'
AND (
  first_name IS NULL OR first_name = '' OR
  last_name IS NULL OR last_name = ''
);

-- Populate with defaults
UPDATE targets
SET
  first_name = COALESCE(first_name, 'User'),
  last_name = COALESCE(last_name, ''),
  position = COALESCE(position, ''),
  custom = COALESCE(custom, '')
WHERE campaign = 'campaign_name';