314 lines
11 KiB
Markdown
314 lines
11 KiB
Markdown
# 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
|
||
|
||
```bash
|
||
npm install
|
||
```
|
||
|
||
### 2. Configure environment
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
```
|
||
|
||
Generate an encryption key:
|
||
|
||
```bash
|
||
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](https://openrouter.ai/keys) (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.
|
||
|
||
1. Sign up at [openrouter.ai](https://openrouter.ai/)
|
||
2. Go to [openrouter.ai/keys](https://openrouter.ai/keys) and create an API key
|
||
3. Paste it into `.env` as `AI_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
|
||
|
||
```bash
|
||
npm run db:migrate
|
||
```
|
||
|
||
### 4. Start the server
|
||
|
||
```bash
|
||
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 `currentRamp` per 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:**
|
||
|
||
1. Enable 2-Step Verification on the Google account
|
||
2. Go to [myaccount.google.com/apppasswords](https://myaccount.google.com/apppasswords)
|
||
3. Create an app password for "Mail" → copy the 16-character code
|
||
4. Use that code as `password` in Warmbox
|
||
|
||
##### Yahoo Mail
|
||
|
||
| Field | Value |
|
||
| ---------- | --------------------- |
|
||
| `smtpHost` | `smtp.mail.yahoo.com` |
|
||
| `smtpPort` | `587` |
|
||
| `imapHost` | `imap.mail.yahoo.com` |
|
||
| `imapPort` | `993` |
|
||
|
||
App password: [Yahoo Account Security](https://login.yahoo.com/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](https://account.microsoft.com/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)**
|
||
|
||
```bash
|
||
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`/`imapHost` to your host's values (see table above).
|
||
|
||
**2. Gmail account**
|
||
|
||
```bash
|
||
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**
|
||
|
||
```bash
|
||
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**
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
curl -X POST http://localhost:3000/api/inboxes/{id}/test
|
||
```
|
||
|
||
A successful response looks like:
|
||
|
||
```json
|
||
{
|
||
"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
|
||
|
||
```bash
|
||
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
|
||
|
||
1. **Dispatcher** picks random sender/receiver pairs from active inboxes, generates a B2B email via AI, and sends via SMTP.
|
||
2. **Rescuer** scans spam folders (`[Gmail]/Spam`, `Junk`, `Spam`) for warmup emails, moves them to INBOX, flags them as read/starred/important.
|
||
3. **Rescuer** also processes unread warmup emails already in INBOX, generates contextual AI replies, and sends them back.
|
||
4. Failed IMAP/SMTP connections mark the inbox as `error` and skip it until credentials are fixed.
|
||
|
||
## Production
|
||
|
||
```bash
|
||
npm run build
|
||
npm start
|
||
```
|
||
|
||
Use a process manager (systemd, PM2) and back up the SQLite database file regularly.
|
||
|
||
## License
|
||
|
||
MIT
|