ScamVerify™
Tutorials

Screen Emails for Phishing

Step-by-step tutorial for building an email phishing screening service with Node.js using the ScamVerify™ API.

This tutorial walks you through building an email screening service in Node.js that detects phishing, spoofing, and fraud. You will parse raw emails (including headers), call the ScamVerify™ API, and automatically quarantine high-risk messages.

Prerequisites

  • Node.js 18 or later
  • A ScamVerify™ API key (get one at scamverify.ai/settings/api)
  • Basic understanding of email headers (SPF, DKIM, DMARC)

What You Will Build

An email screening pipeline that:

  1. Receives a raw email with headers
  2. Calls the ScamVerify™ email analysis API
  3. Interprets the header analysis (SPF, DKIM, DMARC checks)
  4. Checks embedded URLs and phone numbers for threats
  5. Auto-quarantines high-risk emails
  6. Returns a detailed screening report

Set up the project

mkdir email-screener && cd email-screener
npm init -y
npm install express dotenv mailparser

Create a .env file:

SCAMVERIFY_API_KEY=sv_live_your_key_here
PORT=3000

Create the ScamVerify™ email client

Build a client module focused on email analysis.

// scamverify.js

class ScamVerifyEmailClient {
  constructor(apiKey) {
    if (!apiKey || !apiKey.startsWith('sv_')) {
      throw new Error('Invalid API key. Must start with sv_live_ or sv_test_');
    }
    this.apiKey = apiKey;
    this.baseUrl = 'https://scamverify.ai/api/v1';
  }

  async analyzeEmail(emailBody, rawHeaders = null) {
    const payload = { email_body: emailBody };

    // Including raw headers enables SPF/DKIM/DMARC analysis
    if (rawHeaders) {
      payload.raw_headers = rawHeaders;
    }

    const response = await fetch(`${this.baseUrl}/email/analyze`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(payload),
    });

    if (!response.ok) {
      const errorMessages = {
        401: 'Invalid or revoked API key',
        402: 'Quota exhausted',
        429: 'Rate limited',
      };
      throw new Error(errorMessages[response.status] || `API error: ${response.status}`);
    }

    return response.json();
  }
}

module.exports = { ScamVerifyEmailClient };

Build the email parser

Extract the body and headers from a raw email. The raw_headers parameter is what enables ScamVerify™ to check SPF, DKIM, and DMARC records.

// email-parser.js
const { simpleParser } = require('mailparser');

async function parseRawEmail(rawEmail) {
  const parsed = await simpleParser(rawEmail);

  // Extract the raw headers (everything before the first blank line)
  const headerEndIndex = rawEmail.indexOf('\r\n\r\n');
  const fallbackIndex = rawEmail.indexOf('\n\n');
  const splitIndex = headerEndIndex !== -1 ? headerEndIndex : fallbackIndex;

  const rawHeaders = splitIndex !== -1
    ? rawEmail.substring(0, splitIndex)
    : null;

  return {
    from: parsed.from?.text || '',
    to: parsed.to?.text || '',
    subject: parsed.subject || '',
    body: parsed.text || parsed.html || '',
    rawHeaders,
    date: parsed.date,
    attachments: parsed.attachments?.length || 0,
  };
}

module.exports = { parseRawEmail };

Build the screening service

Create the main screening logic that interprets ScamVerify™ results and makes quarantine decisions.

// screener.js

// Risk thresholds for auto-quarantine
const QUARANTINE_SCORE = 60;
const REVIEW_SCORE = 35;

function classifyEmail(apiResult, parsedEmail) {
  const { risk_score, verdict, explanation, signals } = apiResult;

  const report = {
    riskScore: risk_score,
    verdict,
    explanation,
    from: parsedEmail.from,
    subject: parsedEmail.subject,
    action: 'deliver',  // default
    flags: [],
  };

  // Check header authentication
  if (signals.header_analysis) {
    const headers = signals.header_analysis;

    if (headers.spf === 'fail') {
      report.flags.push('SPF authentication failed. Sender domain may be spoofed.');
    }
    if (headers.dkim === 'fail') {
      report.flags.push('DKIM signature invalid. Message may have been tampered with.');
    }
    if (headers.dmarc === 'fail') {
      report.flags.push('DMARC policy failure. Domain alignment issue detected.');
    }
    if (headers.return_path_mismatch) {
      report.flags.push('Return path does not match sender domain. Possible spoofing.');
    }
  }

  // Check for brand impersonation
  if (signals.brand_impersonation) {
    report.flags.push(`Brand impersonation detected: ${signals.brand_impersonation}`);
  }

  // Check embedded URLs
  const maliciousUrls = (signals.extracted_urls || [])
    .filter(u => u.risk_score >= 50);
  if (maliciousUrls.length > 0) {
    report.flags.push(
      `${maliciousUrls.length} malicious URL(s) found in email body.`
    );
    report.maliciousUrls = maliciousUrls.map(u => u.url);
  }

  // Check embedded phone numbers
  const riskyPhones = (signals.extracted_phones || [])
    .filter(p => p.risk_score >= 50);
  if (riskyPhones.length > 0) {
    report.flags.push(
      `${riskyPhones.length} suspicious phone number(s) found in email body.`
    );
  }

  // Determine action
  if (risk_score >= QUARANTINE_SCORE) {
    report.action = 'quarantine';
  } else if (risk_score >= REVIEW_SCORE || report.flags.length >= 2) {
    report.action = 'review';
  }

  return report;
}

module.exports = { classifyEmail, QUARANTINE_SCORE, REVIEW_SCORE };

Wire it all together in Express

Create the server with endpoints for screening raw emails and viewing quarantine status.

// server.js
require('dotenv').config();
const express = require('express');
const { ScamVerifyEmailClient } = require('./scamverify');
const { parseRawEmail } = require('./email-parser');
const { classifyEmail } = require('./screener');

const app = express();
app.use(express.json({ limit: '5mb' }));  // Emails can be large
app.use(express.text({ type: 'message/rfc822', limit: '5mb' }));

const client = new ScamVerifyEmailClient(process.env.SCAMVERIFY_API_KEY);

// In-memory store for demo purposes (use a database in production)
const quarantine = [];
const reviewQueue = [];

// POST /screen - Screen a raw email
app.post('/screen', async (req, res) => {
  let emailBody, rawHeaders;

  if (typeof req.body === 'string') {
    // Raw email submitted as text
    const parsed = await parseRawEmail(req.body);
    emailBody = parsed.body;
    rawHeaders = parsed.rawHeaders;
  } else {
    // JSON with email_body and optional raw_headers
    emailBody = req.body.email_body;
    rawHeaders = req.body.raw_headers;
  }

  if (!emailBody) {
    return res.status(400).json({
      error: 'Missing email content',
      hint: 'Send raw email as text/plain or JSON with email_body field',
    });
  }

  try {
    // Call ScamVerify API
    const apiResult = await client.analyzeEmail(emailBody, rawHeaders);

    // Classify and determine action
    const parsed = await parseRawEmail(
      rawHeaders ? `${rawHeaders}\r\n\r\n${emailBody}` : emailBody
    );
    const report = classifyEmail(apiResult, parsed);

    // Execute the action
    if (report.action === 'quarantine') {
      quarantine.push({ ...report, timestamp: new Date().toISOString() });
      console.log(`QUARANTINED: ${parsed.subject} from ${parsed.from}`);
    } else if (report.action === 'review') {
      reviewQueue.push({ ...report, timestamp: new Date().toISOString() });
      console.log(`FLAGGED FOR REVIEW: ${parsed.subject} from ${parsed.from}`);
    }

    return res.json({
      success: true,
      action: report.action,
      riskScore: report.riskScore,
      verdict: report.verdict,
      explanation: report.explanation,
      flags: report.flags,
    });
  } catch (error) {
    console.error('Email screening failed:', error.message);
    return res.status(500).json({ error: 'Screening failed. ' + error.message });
  }
});

// POST /screen/json - Screen with pre-parsed email body
app.post('/screen/json', async (req, res) => {
  const { email_body, raw_headers } = req.body;

  if (!email_body) {
    return res.status(400).json({ error: 'Missing email_body field' });
  }

  try {
    const apiResult = await client.analyzeEmail(email_body, raw_headers);
    const report = classifyEmail(apiResult, {
      from: req.body.from || 'unknown',
      subject: req.body.subject || 'unknown',
    });

    if (report.action === 'quarantine') {
      quarantine.push({ ...report, timestamp: new Date().toISOString() });
    } else if (report.action === 'review') {
      reviewQueue.push({ ...report, timestamp: new Date().toISOString() });
    }

    return res.json({ success: true, ...report });
  } catch (error) {
    return res.status(500).json({ error: error.message });
  }
});

// GET /quarantine - View quarantined emails
app.get('/quarantine', (req, res) => {
  res.json({ count: quarantine.length, emails: quarantine });
});

// GET /review - View emails flagged for review
app.get('/review', (req, res) => {
  res.json({ count: reviewQueue.length, emails: reviewQueue });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Email screening server running on port ${PORT}`);
});

Test the screening service

Start the server and send test emails.

node server.js

Test with a JSON request (simplest approach):

# Screen an email body
curl -X POST http://localhost:3000/screen/json \
  -H "Content-Type: application/json" \
  -d '{
    "from": "security@example-bank.com",
    "subject": "Urgent: Account Verification Required",
    "email_body": "Dear Customer,\n\nWe have detected unusual activity on your account. Please verify your identity immediately by clicking the link below:\n\nhttp://example-phishing-site.com/verify\n\nIf you do not verify within 24 hours, your account will be suspended.\n\nRegards,\nSecurity Team",
    "raw_headers": "From: security@example-bank.com\nTo: user@company.com\nSubject: Urgent: Account Verification Required\nReceived-SPF: fail"
  }'

Test with a clean email:

curl -X POST http://localhost:3000/screen/json \
  -H "Content-Type: application/json" \
  -d '{
    "from": "colleague@company.com",
    "subject": "Meeting tomorrow",
    "email_body": "Hi, just wanted to confirm our meeting tomorrow at 2 PM. Let me know if the time still works for you."
  }'

# Check quarantine
curl http://localhost:3000/quarantine

Understanding Header Analysis

If you do not include the raw_headers parameter, the API will skip SPF/DKIM/DMARC analysis. For the most accurate phishing detection, always include raw email headers.

The ScamVerify™ email analysis checks these authentication headers:

Header CheckWhat It Detects
SPF (Sender Policy Framework)Whether the sending server is authorized to send on behalf of the domain
DKIM (DomainKeys Identified Mail)Whether the email content has been modified in transit
DMARC (Domain-based Message Authentication)Whether SPF and DKIM align with the From domain
Return-Path mismatchWhether the bounce address differs from the visible sender

When any of these checks fail, the risk score is automatically boosted. SPF or DKIM failures add +15 to the risk score, and return path mismatches add +10. Multiple failures compound.

Complete Project Structure

email-screener/
  .env                  # API key and port
  scamverify.js         # API client for email analysis
  email-parser.js       # Raw email parsing with mailparser
  screener.js           # Classification logic and quarantine thresholds
  server.js             # Express server with /screen and /quarantine endpoints
  package.json

Next Steps

On this page