Skip to content

postalsys/bounce-classifier

Repository files navigation

@postalsys/bounce-classifier

SMTP bounce message classifier using machine learning. Classifies email bounce/error messages into 16 categories.

Works in both Node.js and browsers - runs entirely client-side with no server required. Zero runtime dependencies.

Live Demo

Note

This classifier was created for EmailEngine, a self-hosted email gateway that allows making REST requests against email accounts. For more information about how bounce classification integrates with EmailEngine, see the messageBounce webhook documentation.

Installation

npm install @postalsys/bounce-classifier

Usage

ES Modules (Browser & Node.js)

import { classify, initialize } from "@postalsys/bounce-classifier";

// Optional: pre-load the model
await initialize();

const result = await classify("550 5.1.1 User Unknown");
console.log(result.label); // 'user_unknown'
console.log(result.confidence); // 0.95
console.log(result.action); // 'remove'

CommonJS (Node.js)

const { classify } = require("@postalsys/bounce-classifier");

async function main() {
  const result = await classify("550 5.1.1 User Unknown");
  console.log(result);
}

main();

Browser Usage

<script type="module">
  import { classify, initialize } from "./src/index.js";

  // Specify model path for browser
  await initialize({ modelPath: "./model" });

  const result = await classify("550 5.1.1 User Unknown");
  console.log(result);
</script>

See the example/ folder for a complete standalone browser demo that works offline.

API

initialize(options?): Promise<void>

Pre-load the model and vocabulary. Called automatically on first classification.

// Node.js - uses bundled model automatically
await initialize();

// Browser - specify model path
await initialize({ modelPath: "./path/to/model" });

classify(message: string): Promise<ClassificationResult>

Classify a single bounce message.

const result = await classify("450 Greylisted, try again in 5 minutes");
// {
//   label: 'greylisting',
//   confidence: 0.947,
//   action: 'retry',
//   retryAfter: 300,  // seconds (only if timing found in message)
//   scores: { ... }
// }

const result2 = await classify("550 blocked using zen.spamhaus.org");
// {
//   label: 'ip_blacklisted',
//   confidence: 0.958,
//   action: 'retry_different_ip',
//   blocklist: { name: 'Spamhaus ZEN', type: 'ip' },
//   scores: { ... }
// }

getLabels(): Promise<string[]>

Get list of all possible classification labels.

const labels = await getLabels();
// ['auth_failure', 'domain_blacklisted', 'geo_blocked', ...]

isReady(): boolean

Check if the classifier is initialized.

reset(): void

Reset classifier state for re-initialization.

Helper Functions

import {
  extractRetryTiming,
  identifyBlocklist,
  getAction,
  extractSmtpCodes,
} from "@postalsys/bounce-classifier";

// Extract retry timing from message
const seconds = extractRetryTiming("try again in 5 minutes");
// 300

// Identify blocklists mentioned
const blocklist = identifyBlocklist("blocked by zen.spamhaus.org");
// { name: 'Spamhaus ZEN', type: 'ip' }

// Get recommended action for a label
const action = getAction("mailbox_full");
// 'retry'

// Extract SMTP codes
const codes = extractSmtpCodes("550 5.1.1 User unknown");
// { mainCode: '550', extendedCode: '5.1.1' }

Labels

Label Description Action
user_unknown Recipient doesn't exist remove
invalid_address Bad syntax, domain not found remove
mailbox_disabled Account suspended/disabled remove
mailbox_full Over quota, storage exceeded retry
greylisting Temporary rejection, retry later retry
rate_limited Too many connections/messages retry
server_error Timeout, connection failed retry
ip_blacklisted Sender IP on RBL retry_different_ip
domain_blacklisted Sender domain on blocklist fix_configuration
auth_failure DMARC/SPF/DKIM failure fix_configuration
relay_denied Relaying not permitted fix_configuration
spam_blocked Message detected as spam review
policy_blocked Local policy rejection review
virus_detected Infected content detected remove_content
geo_blocked Geographic/country-based rejection retry_different_ip
unknown Unclassified bounce type review

SMTP Code Fallback

When the ML model has low confidence (< 50%), the classifier falls back to SMTP status code-based classification using RFC 3463 enhanced status codes. This ensures reliable classification even for messages the model hasn't seen.

const result = await classify("550 5.2.2 Over quota");
// If ML confidence is low, uses 5.2.2 -> mailbox_full fallback
// result.usedFallback will be true

Running the Demo

The example/ folder contains a browser demo. To run it:

cd example
npx serve ..
# Open http://localhost:3000/example/ in your browser

Model Details

  • Architecture: Embedding + GlobalAveragePooling + Dense layers
  • Vocabulary size: 5,000 tokens
  • Max sequence length: 100 tokens
  • Validation accuracy: ~95%
  • Model size: ~1.3 MB
  • Runtime: Pure JavaScript (no native dependencies)

License

MIT License - Copyright (c) Postal Systems OU

About

Bounce classifier

Resources

License

Stars

Watchers

Forks

Sponsor this project

  •  

Packages

No packages published

Contributors 2

  •  
  •