Automated job aggregation from Gmail to Discord.
Architecture •
Quick Start •
Configuration
Employment Bot monitors a Gmail inbox for job notification emails, extracts job posting URLs, scrapes detailed information from each link, and posts formatted job listings to a Discord channel. It runs on a schedule with immediate feedback—each job appears in Discord seconds after being scraped.
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Gmail │────▶│ Job Links │────▶│ Scraper │────▶│ Firestore │────┐
│ Emails │ │ Extracted │ │ (1-3 sec) │ │ (pending) │ │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│
┌───────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────┐ ┌─────────────┐
│ Discord │◀────│ Job Embed │
│ Channel │ │ Formatted │
└─────────────┘ └─────────────┘
The bot processes jobs individually through a streaming pipeline:
- Fetch — Poll Gmail API for unread emails from job notification services
- Extract — Parse HTML emails to extract job posting URLs
- Scrape — Use Playwright to visit each job URL and extract structured data
- Post — Format as Discord embed and send to configured channel
- Rate Limit — Wait configurable delay before next job (respects Discord's 5 msg/5s limit)
Each job appears in Discord within ~4 seconds of being scraped, rather than waiting for batch completion.
- Startup: Runs immediately when the bot connects (configurable via
RUN_ON_STARTUP) - Cron: Every 20 minutes (
:00,:20,:40in America/Toronto timezone)
- Node.js 20+
- Gmail OAuth credentials (for job email access)
- Firebase project (for job persistence)
- Discord bot token
# Clone and install
npm install
# Configure environment
cp .env.example .env
# Edit .env with your credentials
# Run the bot
npm run discord:devSee AGENTS.md for complete environment setup including Gmail OAuth flow.
docker compose up --build -d # Start
docker compose down # StopCreate .env in the project root:
| Variable | Description |
|---|---|
DISCORD_BOT_TOKEN |
Discord bot authentication |
APPLICATION_ID |
Discord application ID |
GUILD_ID |
Discord server ID |
JOB_CHANNEL_ID |
Target channel for job postings |
GMAIL_CLIENT_ID |
Gmail OAuth client ID |
GMAIL_CLIENT_SECRET |
Gmail OAuth client secret |
GMAIL_REFRESH_TOKEN |
Gmail OAuth refresh token |
FIREBASE_ADMIN_CONFIG |
Firebase service account JSON |
SCRAPE_COOLDOWN_MS |
Delay between jobs (default: 1000ms) |
RUN_ON_STARTUP |
Run pipeline on bot startup (default: true) |
employment-bot/
├── apps/
│ ├── discord/ # Discord bot (pipeline orchestration)
│ └── cli/ # CLI tool for testing (employ-cli)
├── packages/
│ ├── ai/ # AI utilities
│ ├── database/ # Firestore service
│ ├── email/ # Gmail OAuth and API
│ ├── job-pipeline/ # Scrape orchestration
│ ├── scraper/ # Playwright-based scraping
│ └── shared/ # Logger, models, constants
├── .env
├── compose.yaml
└── Dockerfile
Test scraper and email fetching without database writes:
# Install globally
npm link --workspace=apps/cli
# Scrape a URL
employ-cli url "https://company.com/jobs/123" --json
# Check emails
employ-cli emails --scrape --limit 3| Script | Description |
|---|---|
npm run discord |
Run Discord bot (production) |
npm run discord:dev |
Run with hot reload |
npm run cli |
Run CLI tool |
npm test |
Run test suite |
npm run test:watch |
Tests in watch mode |
npm run test:coverage |
Tests with coverage |
MIT