Add a new field `consecutiveFailures` to the Inbox model to track consecutive errors. Update the inbox guard logic to mark mailboxes as error after exceeding a failure threshold. Implement retry logic in email and IMAP services to handle transient connection issues. Adjust job processing to reclaim stale send jobs and improve overall reliability of email dispatching. Update routes to initialize `consecutiveFailures` on inbox creation and reset it when the inbox status changes to active. |
||
|---|---|---|
| apps | ||
| docs | ||
| packages/shared | ||
| prisma | ||
| scripts | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| .woodpecker.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| package-lock.json | ||
| package.json | ||
| prisma.config.ts | ||
| README.md | ||
| tsconfig.json | ||
Warmbox
Self-hosted email warmup and deliverability engine for custom domains. Uses SMTP to send realistic B2B conversations and IMAP to rescue messages from spam folders, flag them as important, and reply — improving sender reputation over time.
Tech Stack
- Runtime: Node.js + TypeScript + Express
- Database: SQLite via Prisma ORM
- Email: nodemailer (SMTP), imapflow (IMAP)
- AI: OpenRouter (free models) for conversation generation
- Scheduler: node-cron
Quick Start
1. Install dependencies
npm install
2. Configure environment
cp .env.example .env
Generate an encryption key:
openssl rand -hex 32
Set these in .env:
| Variable | Description |
|---|---|
DATABASE_URL |
SQLite path, e.g. file:./prisma/dev.db (configured in prisma.config.ts) |
ENCRYPTION_KEY |
64-char hex string (32 bytes) for AES-256-GCM password encryption |
AI_API_KEY |
API key from OpenRouter (free tier works) |
AI_BASE_URL |
Default https://openrouter.ai/api/v1 |
AI_MODEL |
Default openrouter/free (auto-picks a free model) |
API_KEY |
Optional API key for REST auth (x-api-key header) |
PORT |
HTTP port (default 3000) |
Get a free AI API key (OpenRouter)
Email generation only needs a small, fast language model — not GPT-4 class reasoning.
- Sign up at openrouter.ai
- Go to openrouter.ai/keys and create an API key
- Paste it into
.envasAI_API_KEY
Recommended free models (set AI_MODEL in .env):
| Model | Notes |
|---|---|
openrouter/free |
Auto-selects an available free model (default) |
google/gemma-2-9b-it:free |
Good quality, reliable JSON |
meta-llama/llama-3.2-3b-instruct:free |
Fast, lightweight |
You do not need an OpenAI account.
3. Run database migrations
npm run db:migrate
4. Start the server
npm run dev
The API and cron jobs start together. Default schedules:
- Dispatcher: every 15 minutes — sends warmup emails
- Rescuer: every 10 minutes — rescues spam, replies to inbox
- Ramp: midnight daily — increases
currentRampper inbox
5. Add mailboxes
You need at least 2 active inboxes for warmup to work. Warmbox randomly pairs senders and receivers from your pool.
SMTP/IMAP settings by provider
Every inbox needs 4 connection fields plus credentials:
| Field | What it is |
|---|---|
smtpHost / smtpPort |
Where to send mail |
imapHost / imapPort |
Where to read mail (spam rescue + replies) |
username |
Usually the full email address |
password |
App password (not your login password) |
Gmail (including Google Workspace custom domains)
If hello@fillcareapp.com is hosted on Google Workspace, use Gmail servers:
| Field | Value |
|---|---|
smtpHost |
smtp.gmail.com |
smtpPort |
587 |
imapHost |
imap.gmail.com |
imapPort |
993 |
App password setup:
- Enable 2-Step Verification on the Google account
- Go to myaccount.google.com/apppasswords
- Create an app password for "Mail" → copy the 16-character code
- Use that code as
passwordin Warmbox
Yahoo Mail
| Field | Value |
|---|---|
smtpHost |
smtp.mail.yahoo.com |
smtpPort |
587 |
imapHost |
imap.mail.yahoo.com |
imapPort |
993 |
App password: Yahoo Account Security → Generate app password.
Outlook / Hotmail / Live.com (@live.com, @outlook.com, @hotmail.com)
| Field | Value |
|---|---|
smtpHost |
smtp-mail.outlook.com |
smtpPort |
587 |
imapHost |
outlook.office365.com |
imapPort |
993 |
App password: Microsoft Account Security → Advanced security → App passwords. IMAP must be enabled in Outlook settings.
Custom domain (cPanel / generic hosting)
If hello@fillcareapp.com is on cPanel or similar (not Google/Microsoft), check your host's email docs. Common pattern:
| Field | Value |
|---|---|
smtpHost |
mail.fillcareapp.com or smtp.fillcareapp.com |
smtpPort |
587 (or 465 for SSL) |
imapHost |
mail.fillcareapp.com or imap.fillcareapp.com |
imapPort |
993 |
Ask your DNS/hosting provider if unsure — look for "IMAP/SMTP settings" in cPanel → Email Accounts.
Example: FillCare + personal accounts
Add hello@fillcareapp.com as your primary warmup domain (industry: Childcare), then add 4–5 Gmail/Yahoo/Live accounts as the reply pool.
1. Primary domain (FillCare)
curl -X POST http://localhost:3000/api/inboxes \
-H "Content-Type: application/json" \
-d '{
"email": "hello@fillcareapp.com",
"smtpHost": "smtp.gmail.com",
"smtpPort": 587,
"imapHost": "imap.gmail.com",
"imapPort": 993,
"username": "hello@fillcareapp.com",
"password": "hkem mfmi bkmg bqyw",
"industry": "Childcare",
"dailyLimit": 40,
"currentRamp": 5
}'
If FillCare email is not on Google Workspace, swap
smtpHost/imapHostto your host's values (see table above).
2. Gmail account
curl -X POST http://localhost:3000/api/inboxes \
-H "Content-Type: application/json" \
-d '{
"email": "you@gmail.com",
"smtpHost": "smtp.gmail.com",
"smtpPort": 587,
"imapHost": "imap.gmail.com",
"imapPort": 993,
"username": "you@gmail.com",
"password": "your-gmail-app-password",
"industry": "SaaS"
}'
3. Yahoo account
curl -X POST http://localhost:3000/api/inboxes \
-H "Content-Type: application/json" \
-d '{
"email": "you@yahoo.com",
"smtpHost": "smtp.mail.yahoo.com",
"smtpPort": 587,
"imapHost": "imap.mail.yahoo.com",
"imapPort": 993,
"username": "you@yahoo.com",
"password": "your-yahoo-app-password",
"industry": "Marketing"
}'
4. Live.com / Outlook account
curl -X POST http://localhost:3000/api/inboxes \
-H "Content-Type: application/json" \
-d '{
"email": "you@live.com",
"smtpHost": "smtp-mail.outlook.com",
"smtpPort": 587,
"imapHost": "outlook.office365.com",
"imapPort": 993,
"username": "you@live.com",
"password": "your-microsoft-app-password",
"industry": "Finance"
}'
Repeat for each additional Gmail/Yahoo/Live account. Mixing providers is fine — Warmbox only needs them all in the same pool.
6. Test connections
After adding each inbox, verify SMTP + IMAP:
curl -X POST http://localhost:3000/api/inboxes/{id}/test
A successful response looks like:
{
"ok": true,
"smtp": { "ok": true },
"imap": { "ok": true }
}
If either fails, double-check the app password and that IMAP is enabled on the account.
7. Monitor logs
curl http://localhost:3000/api/logs
Watch for status progression: sent → rescued_from_spam → replied → complete.
API Reference
All endpoints under /api accept optional x-api-key header when API_KEY is set.
| Method | Path | Description |
|---|---|---|
POST |
/api/inboxes |
Create inbox |
GET |
/api/inboxes |
List inboxes |
GET |
/api/inboxes/:id |
Get inbox |
PATCH |
/api/inboxes/:id |
Update inbox |
DELETE |
/api/inboxes/:id |
Delete inbox |
POST |
/api/inboxes/:id/test |
Test SMTP + IMAP |
GET |
/api/logs |
List warmup logs (?status=&inboxId=&page=&limit=) |
GET |
/api/settings |
Get settings |
PATCH |
/api/settings |
Update settings |
GET |
/health |
Health check |
Project Structure
src/
├── config.ts # Environment validation
├── index.ts # Express + scheduler bootstrap
├── jobs/
│ ├── dispatcher.job.ts # Sends warmup emails
│ ├── rescuer.job.ts # Spam rescue + replies
│ └── scheduler.ts # Cron wiring
├── lib/ # Prisma, logger, inbox guard
├── middleware/ # Auth, error handling
├── models/ # Enums, types
├── routes/ # REST endpoints
└── services/
├── ai.service.ts # OpenRouter conversation generator
├── crypto.service.ts # AES-256-GCM encryption
├── email.service.ts # SMTP via nodemailer
├── imap.service.ts # IMAP via imapflow
└── settings.service.ts
prisma/
└── schema.prisma # Database schema
How It Works
- Dispatcher picks random sender/receiver pairs from active inboxes, generates a B2B email via AI, and sends via SMTP.
- Rescuer scans spam folders (
[Gmail]/Spam,Junk,Spam) for warmup emails, moves them to INBOX, flags them as read/starred/important. - Rescuer also processes unread warmup emails already in INBOX, generates contextual AI replies, and sends them back.
- Failed IMAP/SMTP connections mark the inbox as
errorand skip it until credentials are fixed.
Production
npm run build
npm start
Use a process manager (systemd, PM2) and back up the SQLite database file regularly.
License
MIT