Email Classification
Click to zoom
Email Classification
Smart email router that uses AI to automatically categorize and route emails to appropriate handlers using branching logic.
Automate your email processing with AI-powered classification. This example demonstrates how to build intelligent email routing workflows.
How It Works
- AI Classification: Uses GPT to analyze email content and intent
- Smart Routing: Routes to sales, support, or spam based on classification
- IMAP Integration: Works with any email provider
- Branching Logic: Conditional workflows based on AI decisions
Great for building automated customer support systems, lead routing, or any email processing automation.
Requirements
- OpenAI API key
- IMAP-enabled email account
Key Files
yaml
# Note: This example is API-only and does not include a frontend.
version: "1.0"
workflows:
- id: email-classification-test
path: ./habit.yaml
enabled: true
server:
port: 13000
host: "0.0.0.0"yaml
# Email Classification Test with Greenmail
#
# Uses local Greenmail server for email testing
# Also supports direct HTTP submission via habits.input.submission
id: email-classification-test
name: Email Classification Test (Greenmail)
description: Test email classification with local Greenmail server or direct HTTP submission
version: "1.0"
# Accept email data as direct input (alternative to IMAP trigger)
input:
- id: submission
type: object
description: Direct email submission with from, subject, and body fields
required: false
nodes:
# --- Entry Point ---
# Check if we have a direct submission or need to fetch from IMAP
- id: check-input-source
type: bits
position:
x: 100
y: 100
data:
label: Check Input Source
framework: bits
source: npm
module: "@ha-bits/bit-if"
resource: branch
operation: branch
isTrigger: true
params:
mode: if
branches:
- label: "Has Submission"
value1: "{{habits.input.submission}}"
operator: isNotNull
output: "submission"
includeElse: true
defaultOutput: "imap"
# --- HTTP Submission Path ---
# Format the direct submission to match IMAP output structure
- id: format-submission
type: script
position:
x: 300
y: 0
data:
label: Format Submission Input
framework: script
source: inline
params:
emailFrom: "{{habits.input.submission.from}}"
emailSubject: "{{habits.input.submission.subject}}"
emailBody: "{{habits.input.submission.body}}"
type: script
language: deno
script: |
export async function main(emailFrom: string, emailSubject: string, emailBody: string) {
return {
emails: [{
id: "submission-" + Date.now(),
from: emailFrom || "unknown@submission.local",
to: "user@example.com",
subject: emailSubject || "No Subject",
body: emailBody || "",
date: new Date().toISOString(),
source: "http-submission"
}],
count: 1,
folder: "SUBMISSION",
inputSource: "http-submission"
};
}
stopAfterIf:
expr: "{{check-input-source.output}} !== 'submission'"
skipIfStopped: true
# --- IMAP Path ---
# Receive email via IMAP from Greenmail
- id: receive-email-imap
type: bits
position:
x: 300
y: 200
data:
label: Receive Email (Greenmail IMAP)
framework: bits
source: npm
module: "@ha-bits/bit-email"
resource: newEmail
operation: newEmail
params:
folder: "INBOX"
unreadOnly: false
limit: 1
credentials:
email:
imapHost: "localhost"
imapPort: 3143
imapUser: "test@localhost"
imapPassword: "test"
stopAfterIf:
expr: "{{check-input-source.output}} !== 'imap'"
skipIfStopped: true
# --- Merge Point ---
# Merge both paths into a single data stream using any-of
# Uses inputs array with passThrough mode - first non-skipped input is used
- id: any-input
type: bits
position:
x: 500
y: 100
data:
label: Any Input (IMAP or Submission)
framework: bits
source: npm
module: "@ha-bits/bit-any-of"
resource: race
operation: race
params:
passThrough: true
inputs:
- "{{format-submission}}"
- "{{receive-email-imap}}"
# Step 2: Classify email importance with OpenAI
- id: classify-email
type: bits
position:
x: 700
y: 100
data:
label: Classify Email Importance
framework: bits
source: npm
module: "@ha-bits/bit-openai"
resource: chatCompletion
operation: chatCompletion
params:
model: "gpt-4o-mini"
systemPrompt: |
You are an email importance classifier. Analyze the email and classify it into one of three categories:
- "important": Urgent emails requiring immediate attention (security alerts, critical business issues, emergencies)
- "semi-important": Emails that need attention but not urgently (meeting requests, project updates, customer inquiries)
- "not-important": Low priority emails (newsletters, promotional content, general notifications)
Respond with ONLY one word: "important", "semi-important", or "not-important"
userMessage: |
From: {{any-input.result.emails.0.from}}
Subject: {{any-input.result.emails.0.subject}}
Body: {{any-input.result.emails.0.body}}
temperature: 0.1
maxTokens: 10
credentials:
openai:
apiKey: "{{habits.env.OPENAI_API_KEY}}"
# Step 3: Branch based on classification
- id: route-email
type: bits
position:
x: 950
y: 100
data:
label: Route by Importance
framework: bits
source: npm
module: "@ha-bits/bit-if"
resource: branch
operation: branch
params:
mode: switch
branches:
- label: "Important"
value1: "{{classify-email.content}}"
operator: contains
value2: "important"
output: "important"
- label: "Semi-Important"
value1: "{{classify-email.content}}"
operator: contains
value2: "semi-important"
output: "semi-important"
- label: "Not Important"
value1: "{{classify-email.content}}"
operator: contains
value2: "not-important"
output: "not-important"
includeElse: true
defaultOutput: "not-important"
# Step 4a: Send to Telegram for IMPORTANT emails
- id: send-telegram
type: bits
position:
x: 1200
y: -50
data:
label: Send to Telegram (Important)
framework: bits
source: npm
module: "@ha-bits/bit-telegram"
resource: sendMessage
operation: sendMessage
params:
chatId: "{{habits.env.TELEGRAM_CHAT_ID}}"
text: |
🚨 *IMPORTANT EMAIL*
*From:* {{any-input.result.emails.0.from}}
*Subject:* {{any-input.result.emails.0.subject}}
{{any-input.result.emails.0.body}}
parseMode: Markdown
disableNotification: false
credentials:
telegram:
botToken: "{{habits.env.TELEGRAM_BOT_TOKEN}}"
stopAfterIf:
expr: "{{route-email.output}} !== 'important'"
skipIfStopped: true
# Step 4b: Send via SMTP (Greenmail) for SEMI-IMPORTANT emails
- id: send-smtp
type: bits
position:
x: 1200
y: 100
data:
label: Forward via SMTP (Greenmail)
framework: bits
source: npm
module: "@ha-bits/bit-email"
resource: sendEmail
operation: sendEmail
params:
smtpHost: "localhost"
smtpPort: 3025
smtpUser: "forwarder@localhost"
smtpPassword: "forwarder"
from: "forwarder@localhost"
to: "backup@localhost"
subject: "[Semi-Important] FWD: {{any-input.result.emails.0.subject}}"
body: |
This email was classified as semi-important.
Original sender: {{any-input.result.emails.0.from}}
Original subject: {{any-input.result.emails.0.subject}}
---
{{any-input.result.emails.0.body}}
stopAfterIf:
expr: "{{route-email.output}} !== 'semi-important'"
skipIfStopped: true
# Step 4c: Send to WhatsApp for NOT IMPORTANT emails
- id: send-whatsapp
type: bits
position:
x: 1200
y: 250
data:
label: Send to WhatsApp (Not Important)
framework: bits
source: npm
module: "@ha-bits/bit-whatsapp"
resource: sendTextMessage
operation: sendTextMessage
params:
to: "{{habits.env.WHATSAPP_PHONE}}"
message: |
📧 Low-priority email received
From: {{any-input.result.emails.0.from}}
Subject: {{any-input.result.emails.0.subject}}
(Check email for details)
previewUrl: false
credentials:
whatsapp:
accessToken: "{{habits.env.WHATSAPP_ACCESS_TOKEN}}"
phoneNumberId: "{{habits.env.WHATSAPP_PHONE_NUMBER_ID}}"
stopAfterIf:
expr: "{{route-email.output}} !== 'not-important'"
skipIfStopped: true
edges:
# Entry point branches to both paths based on input source check
- source: check-input-source
target: format-submission
sourceHandle: "branch-0"
- source: check-input-source
target: receive-email-imap
sourceHandle: "else"
# Both paths converge at any-input
- source: format-submission
target: any-input
- source: receive-email-imap
target: any-input
# Main flow: any-input -> classify -> route -> actions
- source: any-input
target: classify-email
- source: classify-email
target: route-email
- source: route-email
target: send-telegram
sourceHandle: "branch-0"
- source: route-email
target: send-smtp
sourceHandle: "branch-1"
- source: route-email
target: send-whatsapp
sourceHandle: "branch-2"
output:
classification: "{{classify-email.content}}"
routedTo: "{{route-email.output}}"
inputSource: "{{any-input.triggeredBy}}"
originalEmail:
from: "{{any-input.result.emails.0.from}}"
subject: "{{any-input.result.emails.0.subject}}"example
# Habits Environment Variables Example
# Copy this file to .env and fill in your values
# ============================================================================
# Private Registry Configuration
# ============================================================================
# Verdaccio private registry URL (for publishing bits)
VERDACCIO_REGISTRY_URL=http://localhost:4873
# Verdaccio authentication token (after running: npm adduser --registry http://localhost:4873)
# You can get this from your ~/.npmrc file after authenticating
VERDACCIO_AUTH_TOKEN=
# ============================================================================
# NPM Registry Override (for module loading)
# ============================================================================
# Custom NPM registry URL for loading bits from npm source
# If not set, defaults to https://registry.npmjs.org
# Set to http://localhost:4873 to use local Verdaccio
HABITS_NPM_REGISTRY_URL=http://localhost:4873
# ============================================================================
# Email Classification Example
# ============================================================================
# IMAP Settings (for receiving emails)
IMAP_HOST=imap.gmail.com
IMAP_PORT=993
IMAP_USER=your-email@gmail.com
IMAP_PASSWORD=your-app-password
# SMTP Settings (for sending emails)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASSWORD=your-app-password
BACKUP_EMAIL=backup@example.com
# OpenAI API Key
OPENAI_API_KEY=sk-your-openai-key
# Telegram Settings
TELEGRAM_BOT_TOKEN=your-telegram-bot-token
TELEGRAM_CHAT_ID=your-chat-id
# WhatsApp Settings
WHATSAPP_PHONE=+1234567890
WHATSAPP_ACCESS_TOKEN=your-whatsapp-access-token
WHATSAPP_PHONE_NUMBER_ID=your-phone-number-idQuick Start
Run directly using Cortex package, recommended for production runs, does not inlcude base or extra depdencies.
# First, download the example files
npx @ha-bits/cortex@latest server --config ./email-classification/stack.yaml