Skip to content

Will-gabia/mailgate

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

3 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Mail Gateway

์ง€์ •๋œ IP๋กœ๋ถ€ํ„ฐ SMTP๋ฅผ ํ†ตํ•ด ์ˆ˜์‹ ๋œ ๋ฉ”์ผ์„ ํ—ค๋”/๋ณธ๋ฌธ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ถ„๋ฅ˜ํ•˜์—ฌ ๋‹ด๋‹น์ž์—๊ฒŒ ์ „๋‹ฌํ•˜๊ฑฐ๋‚˜ ๋‚ด๋ถ€ ๋กœ๊ทธ๋กœ ๋‚จ๊ธฐ๋Š” ์‹œ์Šคํ…œ.

๋น ๋ฅธ ์‹œ์ž‘ (Quick Start)

# ๋กœ์ปฌ ์‹คํ–‰ (Node.js + Redis ํ•„์š”)
npm install
cp .env.example .env
npm run db:migrate
npm run dev              # SMTP ์„œ๋ฒ„ (localhost:2525)
npm run dev:api          # API + ๋Œ€์‹œ๋ณด๋“œ (localhost:3000)

# Docker ์‹คํ–‰ (Redis ํฌํ•จ 4๊ฐœ ์ปจํ…Œ์ด๋„ˆ)
docker compose up -d    # SMTP :2525 / API+๋Œ€์‹œ๋ณด๋“œ :3000

๊ธฐ์ˆ  ์Šคํƒ

๊ตฌ๋ถ„ ๊ธฐ์ˆ  ๋น„๊ณ 
๋Ÿฐํƒ€์ž„ Node.js + TypeScript strict ๋ชจ๋“œ
SMTP ์ˆ˜์‹  smtp-server Nodemailer ํ”„๋กœ์ ํŠธ
๋ฉ”์ผ ํŒŒ์‹ฑ mailparser RFC 822 ํ˜ธํ™˜
๋ฉ”์ผ ๋ฐœ์‹  nodemailer ์ „๋‹ฌ/ํฌ์›Œ๋”ฉ
๋น„๋™๊ธฐ ์ž‘์—… ํ BullMQ Redis ๊ธฐ๋ฐ˜ ์ž‘์—… ํ
๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค Redis 7 BullMQ ๋ฐฑ์—”๋“œ, ๋กœ์ปฌ ๋˜๋Š” Docker
API ์„œ๋ฒ„ Hono ๊ฒฝ๋Ÿ‰ Web Framework
๋Œ€์‹œ๋ณด๋“œ React 19 + Vite 7 Tailwind CSS v4
ORM Prisma 6 SQLite โ†’ PostgreSQL/MySQL ์ „ํ™˜ ๊ฐ€๋Šฅ
์„ค์ • ๊ฒ€์ฆ Zod ๋Ÿฐํƒ€์ž„ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๊ฒ€์ฆ
๋กœ๊น… Pino ๊ตฌ์กฐํ™” JSON ๋กœ๊น…

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

mail-gateway/
โ”œโ”€โ”€ prisma/
โ”‚   โ”œโ”€โ”€ schema.prisma              # DB ์Šคํ‚ค๋งˆ ์ •์˜
โ”‚   โ””โ”€โ”€ migrations/                # ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํžˆ์Šคํ† ๋ฆฌ
โ”œโ”€โ”€ scripts/
โ”‚   โ””โ”€โ”€ generate-cert.sh           # ์ž์ฒด์„œ๋ช… TLS ์ธ์ฆ์„œ ์ƒ์„ฑ
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ config/
โ”‚   โ”‚   โ”œโ”€โ”€ shared.ts              # ๊ณตํ†ต ํ™˜๊ฒฝ๋ณ€์ˆ˜ (DB, ๋กœ๊ทธ, Redis ๋“ฑ)
โ”‚   โ”‚   โ”œโ”€โ”€ smtp.ts                # SMTP ์ „์šฉ ํ™˜๊ฒฝ๋ณ€์ˆ˜
โ”‚   โ”‚   โ”œโ”€โ”€ api.ts                 # API ์ „์šฉ ํ™˜๊ฒฝ๋ณ€์ˆ˜
โ”‚   โ”‚   โ”œโ”€โ”€ index.ts               # SMTP ์„ค์ • ํ•˜์œ„ ํ˜ธํ™˜ re-export
โ”‚   โ”‚   โ””โ”€โ”€ logger.ts              # Pino ๋กœ๊ฑฐ ์„ค์ •
โ”‚   โ”œโ”€โ”€ api/
โ”‚   โ”‚   โ”œโ”€โ”€ main.ts                # API ์„œ๋ฒ„ ์—”ํŠธ๋ฆฌํฌ์ธํŠธ (Hono)
โ”‚   โ”‚   โ”œโ”€โ”€ middleware/
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ security.ts        # API Key ์ธ์ฆ + ์†๋„ ์ œํ•œ ๋ฏธ๋“ค์›จ์–ด
โ”‚   โ”‚   โ””โ”€โ”€ routes/
โ”‚   โ”‚       โ”œโ”€โ”€ emails.ts          # ์ด๋ฉ”์ผ ์กฐํšŒ API
โ”‚   โ”‚       โ”œโ”€โ”€ tenants.ts         # ํ…Œ๋„ŒํŠธ CRUD API
โ”‚   โ”‚       โ”œโ”€โ”€ rules.ts           # ๋ฃฐ CRUD API
โ”‚   โ”‚       โ”œโ”€โ”€ forward-logs.ts    # ์ „๋‹ฌ ๋กœ๊ทธ ์กฐํšŒ API
โ”‚   โ”‚       โ””โ”€โ”€ stats.ts           # ๋Œ€์‹œ๋ณด๋“œ ํ†ต๊ณ„ API
โ”‚   โ”œโ”€โ”€ db/
โ”‚   โ”‚   โ”œโ”€โ”€ client.ts              # Prisma ํด๋ผ์ด์–ธํŠธ ์‹ฑ๊ธ€ํ„ด
โ”‚   โ”‚   โ”œโ”€โ”€ repository.ts          # ํ…Œ์ด๋ธ”๋ณ„ CRUD ๋ ˆํฌ์ง€ํ† ๋ฆฌ
โ”‚   โ”‚   โ””โ”€โ”€ index.ts               # ๋ฐฐ๋Ÿด export
โ”‚   โ”œโ”€โ”€ smtp/
โ”‚   โ”‚   โ”œโ”€โ”€ server.ts              # SMTP ์ˆ˜์‹  ์„œ๋ฒ„ (์ˆ˜์‹  โ†’ DB ์ €์žฅ โ†’ ํ ๋“ฑ๋ก๋งŒ ์ˆ˜ํ–‰)
โ”‚   โ”‚   โ””โ”€โ”€ index.ts
โ”‚   โ”œโ”€โ”€ queue/                         # BullMQ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ๊ณ„์ธต
โ”‚   โ”‚   โ”œโ”€โ”€ connection.ts          # IORedis ์—ฐ๊ฒฐ ํŒฉํ† ๋ฆฌ (์‹ฑ๊ธ€ํ„ด)
โ”‚   โ”‚   โ”œโ”€โ”€ email-queue.ts         # BullMQ Queue ํ”„๋กœ๋“€์„œ (addEmailJob)
โ”‚   โ”‚   โ”œโ”€โ”€ email-processor.ts     # BullMQ Worker ์ปจ์Šˆ๋จธ (ํŒŒ์‹ฑโ†’๋ถ„๋ฅ˜โ†’์ „๋‹ฌ)
โ”‚   โ”‚   โ””โ”€โ”€ index.ts               # ๋ฐฐ๋Ÿด export
โ”‚   โ”œโ”€โ”€ parser/
โ”‚   โ”‚   โ””โ”€โ”€ index.ts               # ๋ฉ”์ผ ํŒŒ์‹ฑ (ํ—ค๋”, ๋ณธ๋ฌธ, ์ฒจ๋ถ€)
โ”‚   โ”œโ”€โ”€ keyword/
โ”‚   โ”‚   โ””โ”€โ”€ index.ts               # ํ‚ค์›Œ๋“œ ์ถ”์ถœ ์—”์ง„
โ”‚   โ”œโ”€โ”€ auth/
โ”‚   โ”‚   โ””โ”€โ”€ index.ts               # DKIM/SPF ๊ฒ€์ฆ
โ”‚   โ”œโ”€โ”€ classifier/
โ”‚   โ”‚   โ””โ”€โ”€ index.ts               # ๋ฃฐ ๊ธฐ๋ฐ˜ ๋ถ„๋ฅ˜ ์—”์ง„ (๋ฉ€ํ‹ฐ ํ…Œ๋„ŒํŠธ ์ง€์›)
โ”‚   โ”œโ”€โ”€ retry/
โ”‚   โ”‚   โ””โ”€โ”€ index.ts               # API ์ˆ˜๋™ ์žฌ์‹œ๋„ ์ง€์› (๋ ˆ๊ฑฐ์‹œ)
โ”‚   โ”œโ”€โ”€ storage/
โ”‚   โ”‚   โ””โ”€โ”€ index.ts               # ์ฒจ๋ถ€ํŒŒ์ผ ๋””์Šคํฌ ์ €์žฅ
โ”‚   โ”œโ”€โ”€ forwarder/
โ”‚   โ”‚   โ””โ”€โ”€ index.ts               # ๋ฉ”์ผ ์ „๋‹ฌ + ์ „๋‹ฌ ๋กœ๊ทธ
โ”‚   โ”œโ”€โ”€ rate-limiter/
โ”‚   โ”‚   โ””โ”€โ”€ index.ts               # IP๋ณ„ ์†๋„ ์ œํ•œ
โ”‚   โ”œโ”€โ”€ types/
โ”‚   โ”‚   โ””โ”€โ”€ index.ts               # ๊ณตํ†ต ํƒ€์ž… ์ •์˜
โ”‚   โ”œโ”€โ”€ cli.ts                     # ๋ฃฐ + ํ…Œ๋„ŒํŠธ ๊ด€๋ฆฌ CLI
โ”‚   โ”œโ”€โ”€ worker.ts                  # ๋…๋ฆฝ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค ์—”ํŠธ๋ฆฌํฌ์ธํŠธ (pm2์šฉ)
โ”‚   โ””โ”€โ”€ index.ts                   # SMTP ์—”ํŠธ๋ฆฌํฌ์ธํŠธ (๋‚ด์žฅ ์›Œ์ปค ํฌํ•จ)
โ”œโ”€โ”€ dashboard/                         # React ๋Œ€์‹œ๋ณด๋“œ (Vite + Tailwind CSS)
โ”‚   โ”œโ”€โ”€ src/
โ”‚   โ”‚   โ”œโ”€โ”€ components/Layout.tsx  # ์‚ฌ์ด๋“œ๋ฐ” ๋ ˆ์ด์•„์›ƒ
โ”‚   โ”‚   โ”œโ”€โ”€ pages/                 # ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ๋“ค
โ”‚   โ”‚   โ”œโ”€โ”€ lib/api.ts             # ํƒ€์ž…๋“œ API ํด๋ผ์ด์–ธํŠธ
โ”‚   โ”‚   โ”œโ”€โ”€ types.ts               # ๋Œ€์‹œ๋ณด๋“œ ํƒ€์ž… ์ •์˜
โ”‚   โ”‚   โ””โ”€โ”€ App.tsx                # React Router ์„ค์ •
โ”‚   โ”œโ”€โ”€ vite.config.ts
โ”‚   โ””โ”€โ”€ package.json
โ”œโ”€โ”€ .env.example                   # ํ™˜๊ฒฝ๋ณ€์ˆ˜ ํ…œํ”Œ๋ฆฟ
โ”œโ”€โ”€ Dockerfile                     # SMTP ์„œ๋ฒ„ Docker ๋นŒ๋“œ
โ”œโ”€โ”€ Dockerfile.api                 # API + ๋Œ€์‹œ๋ณด๋“œ Docker ๋นŒ๋“œ
โ”œโ”€โ”€ docker-compose.yml             # Docker Compose ๊ตฌ์„ฑ (Redis ํฌํ•จ)
โ”œโ”€โ”€ .dockerignore                  # Docker ๋นŒ๋“œ ์ œ์™ธ ๋ชฉ๋ก
โ”œโ”€โ”€ tsconfig.json
โ””โ”€โ”€ package.json

์‹œ์ž‘ํ•˜๊ธฐ

์‚ฌ์ „ ์š”๊ตฌ์‚ฌํ•ญ

  • npm
  • Redis 7+ (BullMQ ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค์šฉ, ๋กœ์ปฌ ์„ค์น˜ ๋˜๋Š” Docker)

์„ค์น˜

npm install

ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์„ค์ •

cp .env.example .env
# .env ํŒŒ์ผ์„ ํŽธ์ง‘ํ•˜์—ฌ ๊ฐ’ ์„ค์ •

์ฃผ์š” ํ™˜๊ฒฝ๋ณ€์ˆ˜:

๋ณ€์ˆ˜ ์„ค๋ช… ๊ธฐ๋ณธ๊ฐ’
DATABASE_URL DB ์—ฐ๊ฒฐ ๋ฌธ์ž์—ด file:./data/mail-gateway.db
REDIS_URL Redis ์—ฐ๊ฒฐ URL (BullMQ ํ ๋ฐฑ์—”๋“œ) redis://127.0.0.1:6379
SMTP_HOST SMTP ์ˆ˜์‹  ๋ฐ”์ธ๋“œ ์ฃผ์†Œ 0.0.0.0
SMTP_PORT SMTP ์ˆ˜์‹  ํฌํŠธ 2525
SMTP_ALLOWED_IPS ํ—ˆ์šฉ IP ๋ชฉ๋ก (์‰ผํ‘œ ๊ตฌ๋ถ„, CIDR ์ง€์›) 127.0.0.1
RELAY_SMTP_HOST ๋ฐœ์‹  SMTP ์„œ๋ฒ„ ํ˜ธ์ŠคํŠธ -
RELAY_SMTP_PORT ๋ฐœ์‹  SMTP ํฌํŠธ 587
RELAY_SMTP_USER ๋ฐœ์‹  SMTP ์ธ์ฆ ์‚ฌ์šฉ์ž -
RELAY_SMTP_PASS ๋ฐœ์‹  SMTP ์ธ์ฆ ๋น„๋ฐ€๋ฒˆํ˜ธ -
LOG_LEVEL ๋กœ๊ทธ ๋ ˆ๋ฒจ info
ATTACHMENT_STORAGE_DIR ์ฒจ๋ถ€ํŒŒ์ผ ์ €์žฅ ๋””๋ ‰ํ† ๋ฆฌ ./data/attachments
SMTP_TLS_ENABLED STARTTLS ํ™œ์„ฑํ™” ์—ฌ๋ถ€ false
SMTP_TLS_KEY TLS ๊ฐœ์ธํ‚ค ํŒŒ์ผ ๊ฒฝ๋กœ -
SMTP_TLS_CERT TLS ์ธ์ฆ์„œ ํŒŒ์ผ ๊ฒฝ๋กœ -
SMTP_TLS_CA TLS CA ์ธ์ฆ์„œ ํŒŒ์ผ ๊ฒฝ๋กœ (์„ ํƒ) -
RATE_LIMIT_MAX ์œˆ๋„์šฐ๋‹น IP๋ณ„ ์ตœ๋Œ€ ์—ฐ๊ฒฐ ์ˆ˜ 100
RATE_LIMIT_WINDOW_MS ์†๋„ ์ œํ•œ ์œˆ๋„์šฐ (๋ฐ€๋ฆฌ์ดˆ) 60000
API_HOST API ์„œ๋ฒ„ ๋ฐ”์ธ๋“œ ์ฃผ์†Œ 0.0.0.0
API_PORT API ์„œ๋ฒ„ ํฌํŠธ 3000
MAX_MESSAGE_SIZE_MB ์ˆ˜์‹  ๋ฉ”์ผ ์ตœ๋Œ€ ํฌ๊ธฐ (MB) 25
API_KEY API ์ธ์ฆ ํ‚ค (๋ฏธ์„ค์ • ์‹œ ์ธ์ฆ ๋น„ํ™œ์„ฑํ™”) -
CORS_ORIGINS CORS ํ—ˆ์šฉ ์˜ค๋ฆฌ์ง„ (์‰ผํ‘œ ๊ตฌ๋ถ„, *์€ ์ „์ฒด ํ—ˆ์šฉ) *
API_RATE_LIMIT_MAX API ์œˆ๋„์šฐ๋‹น IP๋ณ„ ์ตœ๋Œ€ ์š”์ฒญ ์ˆ˜ 200
API_RATE_LIMIT_WINDOW_MS API ์†๋„ ์ œํ•œ ์œˆ๋„์šฐ (๋ฐ€๋ฆฌ์ดˆ) 60000

DB ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜

npm run db:migrate

SMTP ์„œ๋ฒ„ + ์›Œ์ปค ์‹คํ–‰

# ๊ฐœ๋ฐœ ๋ชจ๋“œ โ€” ๋‹จ์ผ ํ”„๋กœ์„ธ์Šค (SMTP ์„œ๋ฒ„ + ๋‚ด์žฅ ์›Œ์ปค)
npm run dev
# ๊ฐœ๋ฐœ ๋ชจ๋“œ โ€” ํ”„๋กœ์„ธ์Šค ๋ถ„๋ฆฌ (SMTP ์ˆ˜์‹ ๊ณผ ์ฒ˜๋ฆฌ๋ฅผ ๋…๋ฆฝ ์‹คํ–‰)
npm run dev            # SMTP ์„œ๋ฒ„ (์ˆ˜์‹  + ํ ๋“ฑ๋ก)
npm run dev:worker     # ์›Œ์ปค (ํ ์ฒ˜๋ฆฌ: ํŒŒ์‹ฑโ†’๋ถ„๋ฅ˜โ†’์ „๋‹ฌ)

# ํ”„๋กœ๋•์…˜
npm run build
npm start              # SMTP ์„œ๋ฒ„ + ๋‚ด์žฅ ์›Œ์ปค

# ํ”„๋กœ๋•์…˜ โ€” pm2 ํ”„๋กœ์„ธ์Šค ๋ถ„๋ฆฌ (๊ถŒ์žฅ)
pm2 start dist/index.js --name mail-gateway
pm2 start dist/worker.js --name mail-worker

Tip

ํ”„๋กœ์„ธ์Šค ๋ถ„๋ฆฌ ๊ถŒ์žฅ: ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” SMTP ์ˆ˜์‹ ๊ณผ ์›Œ์ปค๋ฅผ ๋ณ„๋„ ํ”„๋กœ์„ธ์Šค๋กœ ๋ถ„๋ฆฌํ•˜๋ฉด, ์ฒ˜๋ฆฌ ๋กœ์ง์ด ์ˆ˜์‹  ์ฒ˜๋ฆฌ๋Ÿ‰์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์•„ ์ตœ๋Œ€ ์ˆ˜์‹  ์„ฑ๋Šฅ์„ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. pm2 ๋“ฑ์˜ ํ”„๋กœ์„ธ์Šค ๊ด€๋ฆฌ์ž๋กœ ์›Œ์ปค์˜ ๋ฌด์ค‘๋‹จ ์šด์˜์„ ๋ณด์žฅํ•˜์„ธ์š”.

API ์„œ๋ฒ„ + ๋Œ€์‹œ๋ณด๋“œ ์‹คํ–‰

# ๊ฐœ๋ฐœ ๋ชจ๋“œ (API hot reload + Vite dev server)
npm run dev:api              # API ์„œ๋ฒ„ (hot reload)
npm run dev --prefix dashboard   # ๋Œ€์‹œ๋ณด๋“œ (Vite dev, http://localhost:5173)

# ํ”„๋กœ๋•์…˜ (API ์„œ๋ฒ„๊ฐ€ ๋Œ€์‹œ๋ณด๋“œ ์ •์  ํŒŒ์ผ ์„œ๋น™)
npm run build                # TypeScript ์ปดํŒŒ์ผ
npm run build:dashboard      # React ๋Œ€์‹œ๋ณด๋“œ ๋นŒ๋“œ
npm run start:api            # http://localhost:3000 ์—์„œ API + ๋Œ€์‹œ๋ณด๋“œ ์„œ๋น™

๊ธฐํƒ€ ์Šคํฌ๋ฆฝํŠธ

npm run db:studio        # Prisma Studio (DB GUI)
npm run db:generate      # Prisma Client ์žฌ์ƒ์„ฑ
npm run db:migrate:prod  # ํ”„๋กœ๋•์…˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ ์šฉ
npm run cli              # ๋ฃฐ ๊ด€๋ฆฌ CLI
npm run generate-cert    # ์ž์ฒด์„œ๋ช… TLS ์ธ์ฆ์„œ ์ƒ์„ฑ
npm run dev:worker       # ์›Œ์ปค ๊ฐœ๋ฐœ ๋ชจ๋“œ (hot reload)
npm run start:worker     # ์›Œ์ปค ํ”„๋กœ๋•์…˜ ์‹คํ–‰

Docker

Docker๋กœ ์ปจํ…Œ์ด๋„ˆํ™”๋œ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 4๊ฐœ์˜ ์ปจํ…Œ์ด๋„ˆ(Redis, SMTP ์„œ๋ฒ„, BullMQ ์›Œ์ปค, API+๋Œ€์‹œ๋ณด๋“œ)๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.

์ปจํ…Œ์ด๋„ˆ ๊ตฌ์„ฑ

์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€ ์—ญํ•  ํฌํŠธ
redis redis:7-alpine BullMQ ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค 6379
mail-gateway ๋นŒ๋“œ (Dockerfile) SMTP ์ˆ˜์‹  โ†’ DB ์ €์žฅ โ†’ ํ ๋“ฑ๋ก 2525
mail-worker ๋นŒ๋“œ (Dockerfile) BullMQ ์›Œ์ปค (ํŒŒ์‹ฑโ†’๋ถ„๋ฅ˜โ†’์ „๋‹ฌ) -
mail-gateway-api ๋นŒ๋“œ (Dockerfile.api) REST API + React ๋Œ€์‹œ๋ณด๋“œ 3000

๋น ๋ฅธ ์‹œ์ž‘

# ์ด๋ฏธ์ง€ ๋นŒ๋“œ ๋ฐ ์‹คํ–‰ (4๊ฐœ ์ปจํ…Œ์ด๋„ˆ)
docker compose up -d

# ๋กœ๊ทธ ํ™•์ธ
docker compose logs -f redis              # Redis
docker compose logs -f mail-gateway       # SMTP ์„œ๋ฒ„
docker compose logs -f mail-worker        # BullMQ ์›Œ์ปค
docker compose logs -f mail-gateway-api  # API + ๋Œ€์‹œ๋ณด๋“œ
# ์ค‘์ง€
docker compose down

ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์„ค์ •

.env ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๋ฉด docker-compose.yml์ด ์ž๋™์œผ๋กœ ์ฝ์Šต๋‹ˆ๋‹ค:

cp .env.example .env
# .env ํŒŒ์ผ์—์„œ ํ•„์š”ํ•œ ๊ฐ’ ์ˆ˜์ •

๋˜๋Š” docker compose์—์„œ ์ง์ ‘ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œ:

SMTP_ALLOWED_IPS=10.0.0.0/8 RATE_LIMIT_MAX=200 docker compose up -d

๋ณผ๋ฅจ

๋ณผ๋ฅจ ์ปจํ…Œ์ด๋„ˆ ๊ฒฝ๋กœ ์šฉ๋„
mail-data /app/data SQLite DB + ์ฒจ๋ถ€ํŒŒ์ผ ์ €์žฅ (SMTP, ์›Œ์ปค, API ๊ณต์œ )
redis-data /data Redis ์˜์†ํ™” ๋ฐ์ดํ„ฐ
./certs (๋ฐ”์ธ๋“œ) /app/certs TLS ์ธ์ฆ์„œ (์ฝ๊ธฐ ์ „์šฉ, SMTP๋งŒ)

PostgreSQL๋กœ ์ „ํ™˜

docker-compose.yml์—์„œ PostgreSQL ๊ด€๋ จ ์ฃผ์„์„ ํ•ด์ œํ•˜๊ณ  DATABASE_URL์„ ๋ณ€๊ฒฝํ•˜์„ธ์š”:

# docker-compose.yml์—์„œ:
# 1. mail-gateway, mail-worker์˜ DATABASE_URL์„ PostgreSQL URL๋กœ ๋ณ€๊ฒฝ
# 2. depends_on ์„น์…˜ ์ฃผ์„ ํ•ด์ œ
# 3. postgres ์„œ๋น„์Šค ์„น์…˜ ์ฃผ์„ ํ•ด์ œ
# 4. pg-data ๋ณผ๋ฅจ ์ฃผ์„ ํ•ด์ œ

๋˜ํ•œ prisma/schema.prisma์˜ provider๋ฅผ "postgresql"๋กœ ๋ณ€๊ฒฝํ•œ ๋’ค ์ด๋ฏธ์ง€๋ฅผ ๋‹ค์‹œ ๋นŒ๋“œํ•˜์„ธ์š”:

docker compose build --no-cache
docker compose up -d

์ง์ ‘ ๋นŒ๋“œ

# ์ด๋ฏธ์ง€ ๋นŒ๋“œ
docker build -t mail-gateway .
docker build -f Dockerfile.api -t mail-gateway-api .
# Redis ์‹คํ–‰
docker run -d \
  --name mail-gateway-redis \
  -p 6379:6379 \
  -v redis-data:/data \
  redis:7-alpine
# SMTP ์„œ๋ฒ„ ์‹คํ–‰
docker run -d \
  --name mail-gateway \
  -p 2525:2525 \
  -v mail-data:/app/data \
  -e DATABASE_URL=file:/app/data/mail-gateway.db \
  -e REDIS_URL=redis://mail-gateway-redis:6379 \
  -e SMTP_HOST=0.0.0.0 \
  -e SMTP_PORT=2525 \
  -e SMTP_ALLOWED_IPS=127.0.0.1 \
  -e LOG_LEVEL=info \
  --link mail-gateway-redis \
  mail-gateway
# BullMQ ์›Œ์ปค ์‹คํ–‰ (๋™์ผ ์ด๋ฏธ์ง€, ๋‹ค๋ฅธ CMD)
docker run -d \
  --name mail-worker \
  -v mail-data:/app/data \
  -e DATABASE_URL=file:/app/data/mail-gateway.db \
  -e REDIS_URL=redis://mail-gateway-redis:6379 \
  -e LOG_LEVEL=info \
  --link mail-gateway-redis \
  mail-gateway \
  sh -c 'npx prisma migrate deploy && node dist/worker.js'

# API + ๋Œ€์‹œ๋ณด๋“œ ์‹คํ–‰
docker run -d \
  --name mail-gateway-api \
  -p 3000:3000 \
  -v mail-data:/app/data \
  -e DATABASE_URL=file:/app/data/mail-gateway.db \
  -e API_HOST=0.0.0.0 \
  -e API_PORT=3000 \
  -e LOG_LEVEL=info \
  mail-gateway-api

API ์„œ๋ฒ„ + ๋Œ€์‹œ๋ณด๋“œ

SMTP ์„œ๋ฒ„์™€ ๋ณ„๋„๋กœ ์‹คํ–‰๋˜๋Š” REST API ์„œ๋ฒ„์™€ React ๋Œ€์‹œ๋ณด๋“œ์ž…๋‹ˆ๋‹ค. ๋™์ผํ•œ SQLite DB๋ฅผ ๊ณต์œ ํ•˜์—ฌ ๋ฉ”์ผ, ํ…Œ๋„ŒํŠธ, ๋ฃฐ, ์ „๋‹ฌ ๋กœ๊ทธ๋ฅผ ์›น์—์„œ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์•„ํ‚คํ…์ณ

  • API ์„œ๋ฒ„: Hono (Node.js) โ€” /api/* ์—”๋“œํฌ์ธํŠธ ์ œ๊ณต
  • ๋Œ€์‹œ๋ณด๋“œ: React 19 + Vite + Tailwind CSS v4 โ€” SPA
  • ํ”„๋กœ๋•์…˜ ์„œ๋น™: API ์„œ๋ฒ„๊ฐ€ ๋Œ€์‹œ๋ณด๋“œ ์ •์  ํŒŒ์ผ์„ ์ง์ ‘ ์„œ๋น™ (nginx ๋ถˆํ•„์š”)
  • ๊ฐœ๋ฐœ ๋ชจ๋“œ: Vite dev server(ํฌํŠธ 5173)๊ฐ€ API ์„œ๋ฒ„(ํฌํŠธ 3000)๋กœ ํ”„๋ก์‹œ

API ์—”๋“œํฌ์ธํŠธ

๋ฉ”์„œ๋“œ ๊ฒฝ๋กœ ์„ค๋ช…
GET /api/health ํ—ฌ์Šค์ฒดํฌ
GET /api/emails ์ด๋ฉ”์ผ ๋ชฉ๋ก (ํŽ˜์ด์ง€๋„ค์ด์…˜, status/tenantId ํ•„ํ„ฐ)
GET /api/emails/:id ์ด๋ฉ”์ผ ์ƒ์„ธ (์ฒจ๋ถ€ํŒŒ์ผ, ์ „๋‹ฌ ๋กœ๊ทธ ํฌํ•จ)
POST /api/emails/:id/retry ์‹คํŒจ ๋ฉ”์ผ ์žฌ์‹œ๋„ (failed ์ƒํƒœ๋งŒ)
GET /api/tenants ํ…Œ๋„ŒํŠธ ๋ชฉ๋ก
GET /api/tenants/:id ํ…Œ๋„ŒํŠธ ์ƒ์„ธ
POST /api/tenants ํ…Œ๋„ŒํŠธ ์ƒ์„ฑ
PUT /api/tenants/:id ํ…Œ๋„ŒํŠธ ์ˆ˜์ •
DELETE /api/tenants/:id ํ…Œ๋„ŒํŠธ ์‚ญ์ œ
GET /api/rules ๋ฃฐ ๋ชฉ๋ก (tenantId ํ•„ํ„ฐ)
GET /api/rules/:id ๋ฃฐ ์ƒ์„ธ
POST /api/rules ๋ฃฐ ์ƒ์„ฑ
PUT /api/rules/:id ๋ฃฐ ์ˆ˜์ •
DELETE /api/rules/:id ๋ฃฐ ์‚ญ์ œ
GET /api/forward-logs ์ „๋‹ฌ ๋กœ๊ทธ ๋ชฉ๋ก (ํŽ˜์ด์ง€๋„ค์ด์…˜, emailId/status ํ•„ํ„ฐ)
GET /api/stats ๋Œ€์‹œ๋ณด๋“œ ํ†ต๊ณ„ (totalEmails, emailsByStatus, emailsToday ๋“ฑ)

Note

๐Ÿ“– ์ „์ฒด API ์ŠคํŽ™์€ docs/openapi.yaml (OpenAPI 3.0.3) ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”. ์š”์ฒญ/์‘๋‹ต ์Šคํ‚ค๋งˆ, ํŒŒ๋ผ๋ฏธํ„ฐ ์ƒ์„ธ, ์—๋Ÿฌ ์‘๋‹ต ํ˜•์‹์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

์‘๋‹ต ํ˜•์‹

// ์„ฑ๊ณต (๋‹จ์ผ ๊ฐ์ฒด)
{ "data": { ... } }

// ์„ฑ๊ณต (๋ชฉ๋ก + ํŽ˜์ด์ง€๋„ค์ด์…˜)
{ "data": [...], "total": 100, "page": 1, "limit": 20 }

// ์—๋Ÿฌ
{ "error": "์—๋Ÿฌ ๋ฉ”์‹œ์ง€" }

๋Œ€์‹œ๋ณด๋“œ ํŽ˜์ด์ง€

๊ฒฝ๋กœ ํŽ˜์ด์ง€ ์„ค๋ช…
/ Dashboard ํ†ต๊ณ„ ์นด๋“œ, ์ƒํƒœ ๋ถ„ํฌ, ์ตœ๊ทผ ์ด๋ฉ”์ผ
/emails Emails ์ด๋ฉ”์ผ ๋ชฉ๋ก + ์ƒํƒœ ํ•„ํ„ฐ
/emails/:id Email Detail ์ด๋ฉ”์ผ ์ƒ์„ธ + ์ฒจ๋ถ€ํŒŒ์ผ + ์ „๋‹ฌ ๋กœ๊ทธ
/tenants Tenants ํ…Œ๋„ŒํŠธ CRUD ๊ด€๋ฆฌ
/rules Rules ๋ฃฐ CRUD ๊ด€๋ฆฌ + ํ…Œ๋„ŒํŠธ ํ•„ํ„ฐ
/forward-logs Forward Logs ์ „๋‹ฌ ๋กœ๊ทธ ์กฐํšŒ

STARTTLS ์„ค์ •

SMTP ์ˆ˜์‹  ์„œ๋ฒ„์—์„œ STARTTLS๋ฅผ ์ง€์›ํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์™€์˜ ํ†ต์‹ ์„ ์•”ํ˜ธํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

1. ์ธ์ฆ์„œ ์ƒ์„ฑ

๊ฐœ๋ฐœ/ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ๋Š” ์ž์ฒด์„œ๋ช… ์ธ์ฆ์„œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

npm run generate-cert

์ด ์Šคํฌ๋ฆฝํŠธ๋Š” certs/ ๋””๋ ‰ํ† ๋ฆฌ์— key.pem๊ณผ cert.pem์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” Let's Encrypt ๋“ฑ์˜ ๊ณต์ธ ์ธ์ฆ์„œ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.

2. ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์„ค์ •

SMTP_TLS_ENABLED=true
SMTP_TLS_KEY=./certs/key.pem
SMTP_TLS_CERT=./certs/cert.pem
# SMTP_TLS_CA=./certs/ca.pem    # ์„ ํƒ: CA ์ธ์ฆ์„œ ์ฒด์ธ

3. ๋™์ž‘ ๋ฐฉ์‹

  • SMTP_TLS_ENABLED=true ์„ค์ • ์‹œ EHLO ์‘๋‹ต์— STARTTLS๊ฐ€ ๊ด‘๊ณ ๋ฉ๋‹ˆ๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ๋Š” STARTTLS ๋ช…๋ น์œผ๋กœ TLS ์—…๊ทธ๋ ˆ์ด๋“œ๋ฅผ ์š”์ฒญํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • TLS ํ™œ์„ฑํ™” ์‹œ key์™€ cert๋Š” ํ•„์ˆ˜์ด๋ฉฐ, ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด ์„œ๋ฒ„๊ฐ€ ์‹œ์ž‘๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • SMTP_TLS_ENABLED=false(๊ธฐ๋ณธ๊ฐ’)์ด๋ฉด ํ‰๋ฌธ SMTP๋งŒ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

์†๋„ ์ œํ•œ (Rate Limiting)

IP๋ณ„ ์Šฌ๋ผ์ด๋”ฉ ์œˆ๋„์šฐ ๋ฐฉ์‹์˜ ์†๋„ ์ œํ•œ์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

  • RATE_LIMIT_MAX: ์œˆ๋„์šฐ ๊ธฐ๊ฐ„ ๋‚ด IP๋‹น ์ตœ๋Œ€ ์—ฐ๊ฒฐ ํšŸ์ˆ˜ (๊ธฐ๋ณธ๊ฐ’: 100)
  • RATE_LIMIT_WINDOW_MS: ์œˆ๋„์šฐ ๊ธฐ๊ฐ„ (๊ธฐ๋ณธ๊ฐ’: 60000ms = 1๋ถ„)
  • IPv6-mapped IPv4 ์ฃผ์†Œ๋Š” ์ž๋™ ์ •๊ทœํ™”๋ฉ๋‹ˆ๋‹ค (::ffff:192.168.1.1 โ†’ 192.168.1.1)
  • ์ œํ•œ ์ดˆ๊ณผ ์‹œ SMTP ์—ฐ๊ฒฐ์ด ๊ฑฐ๋ถ€๋˜๋ฉฐ, ์—๋Ÿฌ ๋ฉ”์‹œ์ง€์™€ ํ•จ๊ป˜ ์žฌ์‹œ๋„ ๊ฐ€๋Šฅ ์‹œ๊ฐ„์ด ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค

API ๋ณด์•ˆ

API ์„œ๋ฒ„์— ๋Œ€ํ•œ ๋ณด์•ˆ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.

API Key ์ธ์ฆ

  • API_KEY ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜๋ฉด ๋ชจ๋“  API ์š”์ฒญ์— ์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค
  • ์ธ์ฆ ๋ฐฉ์‹: x-api-key ํ—ค๋” ๋˜๋Š” ?api_key= ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ
  • /api/health ์—”๋“œํฌ์ธํŠธ๋Š” ์ธ์ฆ ์—†์ด ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค
  • API_KEY๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์œผ๋ฉด ์ธ์ฆ์ด ๋น„ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค (๊ฐœ๋ฐœ ํ™˜๊ฒฝ์šฉ)

CORS ์„ค์ •

  • CORS_ORIGINS ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ํ—ˆ์šฉํ•  ์˜ค๋ฆฌ์ง„์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค
  • *: ๋ชจ๋“  ์˜ค๋ฆฌ์ง„ ํ—ˆ์šฉ (๊ธฐ๋ณธ๊ฐ’)
  • ์‰ผํ‘œ ๊ตฌ๋ถ„์œผ๋กœ ์—ฌ๋Ÿฌ ์˜ค๋ฆฌ์ง„ ์ง€์ •: http://localhost:5173,https://admin.example.com

API ์†๋„ ์ œํ•œ

  • IP๋ณ„ ์Šฌ๋ผ์ด๋”ฉ ์œˆ๋„์šฐ ๋ฐฉ์‹์œผ๋กœ API ์š”์ฒญ์„ ์ œํ•œํ•ฉ๋‹ˆ๋‹ค
  • API_RATE_LIMIT_MAX: ์œˆ๋„์šฐ๋‹น IP๋ณ„ ์ตœ๋Œ€ ์š”์ฒญ ์ˆ˜ (๊ธฐ๋ณธ๊ฐ’: 200)
  • API_RATE_LIMIT_WINDOW_MS: ์œˆ๋„์šฐ ๊ธฐ๊ฐ„ (๊ธฐ๋ณธ๊ฐ’: 60000ms = 1๋ถ„)
  • ์ œํ•œ ์ดˆ๊ณผ ์‹œ 429 Too Many Requests ์‘๋‹ต๊ณผ Retry-After ํ—ค๋”๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค
  • ์‘๋‹ต ํ—ค๋”์— X-RateLimit-Limit, X-RateLimit-Remaining ํฌํ•จ

์ž…๋ ฅ ๊ฒ€์ฆ

  • ๋ฃฐ ์ƒ์„ฑ/์ˆ˜์ • ์‹œ action ๊ฐ’์€ forward | log | archive | reject ์ค‘ ํ•˜๋‚˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • conditions๋Š” ์œ ํšจํ•œ JSON ๋ฐฐ์—ด์ด์–ด์•ผ ํ•˜๋ฉฐ, ๊ฐ ์กฐ๊ฑด์˜ field, operator, value๋ฅผ Zod๋กœ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค
  • action์ด forward์ผ ๋•Œ forwardTo๋Š” ์œ ํšจํ•œ ์ด๋ฉ”์ผ ํ˜•์‹์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • ํ…Œ๋„ŒํŠธ settings๋Š” ์œ ํšจํ•œ JSON ๊ตฌ์กฐ ({ maxMessageSizeBytes?: number })์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • ์ด๋ฉ”์ผ/์ „๋‹ฌ ๋กœ๊ทธ์˜ status ํ•„ํ„ฐ๋Š” ํ—ˆ์šฉ๋œ ๊ฐ’๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
  • ํŽ˜์ด์ง€๋„ค์ด์…˜ page๋Š” ์ตœ๋Œ€ 1000์œผ๋กœ ์ œํ•œ๋ฉ๋‹ˆ๋‹ค

๋ฉ€ํ‹ฐ ํ…Œ๋„ŒํŠธ

์ˆ˜์‹  ๋ฉ”์ผ์˜ rcptTo ๋„๋ฉ”์ธ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ…Œ๋„ŒํŠธ๋ฅผ ์‹๋ณ„ํ•˜์—ฌ, ํ…Œ๋„ŒํŠธ๋ณ„๋กœ ๋ถ„๋ฅ˜ ๋ฃฐ์„ ๊ฒฉ๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋™์ž‘ ๋ฐฉ์‹

  1. ๋ฉ”์ผ ์ˆ˜์‹  ์‹œ rcptTo์˜ ๋„๋ฉ”์ธ(@ ๋’ค)์„ ์ถ”์ถœ
  2. ๋“ฑ๋ก๋œ ํ…Œ๋„ŒํŠธ์˜ domains ๋ชฉ๋ก๊ณผ ๋งค์นญ
  3. ํ…Œ๋„ŒํŠธ๊ฐ€ ๋งค์นญ๋˜๋ฉด: ํ…Œ๋„ŒํŠธ ์ „์šฉ ๋ฃฐ + ๊ธ€๋กœ๋ฒŒ ๋ฃฐ์„ ํ•จ๊ป˜ ํ‰๊ฐ€
  4. ํ…Œ๋„ŒํŠธ๊ฐ€ ์—†์œผ๋ฉด: ๊ธ€๋กœ๋ฒŒ ๋ฃฐ๋งŒ ํ‰๊ฐ€

ํ…Œ๋„ŒํŠธ CLI ๊ด€๋ฆฌ

# ํ…Œ๋„ŒํŠธ ๋ชฉ๋ก
npm run cli -- tenant list

# ํ…Œ๋„ŒํŠธ ์ƒ์„ธ ์กฐํšŒ
npm run cli -- tenant show <name>

# ํ…Œ๋„ŒํŠธ ์ƒ์„ฑ
npm run cli -- tenant create acme-corp --domains acme.com,acme.io

# ํ…Œ๋„ŒํŠธ ์ˆ˜์ •
npm run cli -- tenant update acme-corp --domains acme.com,acme.io,acme.dev --enabled true

# ํ…Œ๋„ŒํŠธ ์‚ญ์ œ
npm run cli -- tenant delete acme-corp

# ํ…Œ๋„ŒํŠธ ์ „์šฉ ๋ฃฐ ์ƒ์„ฑ (--tenant ์˜ต์…˜)
npm run cli -- create acme-urgent \
  --action forward \
  --forward-to ops@acme.com \
  --priority 100 \
  --category urgent \
  --tenant acme-corp \
  --conditions '[{"field":"subject","operator":"contains","value":"๊ธด๊ธ‰"}]'

๋ฃฐ ๊ด€๋ฆฌ CLI

๋ถ„๋ฅ˜ ๋ฃฐ์„ CLI๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

# ์ „์ฒด ๋ฃฐ ๋ชฉ๋ก
npm run cli -- list

# ํŠน์ • ๋ฃฐ ์ƒ์„ธ ์กฐํšŒ
npm run cli -- show <name>

# ๋ฃฐ ์ƒ์„ฑ
npm run cli -- create urgent-forward \
  --action forward \
  --forward-to ops@example.com \
  --priority 100 \
  --category urgent \
  --conditions '[{"field":"subject","operator":"contains","value":"๊ธด๊ธ‰"}]'

# ๋ฃฐ ์ˆ˜์ •
npm run cli -- update urgent-forward --priority 200 --enabled false

# ๋ฃฐ ์‚ญ์ œ
npm run cli -- delete urgent-forward

๋ฉ”์ผ ์ฒ˜๋ฆฌ ํ๋ฆ„

SMTP ์ˆ˜์‹ ๊ณผ ๋ฉ”์ผ ์ฒ˜๋ฆฌ๊ฐ€ BullMQ ํ๋ฅผ ํ†ตํ•ด ๋น„๋™๊ธฐ๋กœ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. SMTP ์„œ๋ฒ„๋Š” ์ˆ˜์‹ ๊ณผ DB ์ €์žฅ๋งŒ ์ˆ˜ํ–‰ํ•˜๊ณ , ์‹ค์ œ ์ฒ˜๋ฆฌ(ํŒŒ์‹ฑ, ๋ถ„๋ฅ˜, ์ „๋‹ฌ)๋Š” ๋ณ„๋„ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค์—์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

โ”โ”โ” SMTP ์„œ๋ฒ„ ํ”„๋กœ์„ธ์Šค โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
์ˆ˜์‹  (SMTP)
  โ”‚
  โ”œโ”€ IP ํ—ˆ์šฉ ํ™•์ธ
  โ”œโ”€ ์†๋„ ์ œํ•œ ํ™•์ธ
  โ”œโ”€ ํ…Œ๋„ŒํŠธ ๋งค์นญ (rcptTo ๋„๋ฉ”์ธ ๊ธฐ๋ฐ˜)
  โ”‚
  โ–ผ
DB ์ €์žฅ (์ตœ์†Œ ๋ฐ์ดํ„ฐ)
  โ”‚
  โ”œโ”€ envelope (mailFrom, rcptTo, remoteIp)
  โ”œโ”€ rawMessage (RFC822 ์›๋ฌธ)
  โ”œโ”€ status = 'received'
  โ”‚
  โ–ผ
BullMQ ํ ๋“ฑ๋ก
  โ”‚
  โ”œโ”€ emailId๋ฅผ jobId๋กœ ์‚ฌ์šฉ (๋ฉฑ๋“ฑ์„ฑ ๋ณด์žฅ)
  โ”œโ”€ ์„ฑ๊ณต โ†’ 250 OK ์‘๋‹ต
  โ””โ”€ ์‹คํŒจ โ†’ 451 Temporary failure ์‘๋‹ต

โ”โ”โ” Redis (BullMQ ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค) โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”

โ”โ”โ” BullMQ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”

์›Œ์ปค๊ฐ€ ํ์—์„œ ์ž‘์—… ์ˆ˜์‹ 
  โ”‚
  โ”œโ”€ ๋ฉฑ๋“ฑ์„ฑ ํ™•์ธ (์ด๋ฏธ ์ฒ˜๋ฆฌ๋œ ์ด๋ฉ”์ผ ๊ฑด๋„ˆ๋›ฐ๊ธฐ)
  โ”‚
  โ–ผ
ํŒŒ์‹ฑ (mailparser)
  โ”‚
  โ”œโ”€ ํ—ค๋”, ๋ณธ๋ฌธ, ์ฒจ๋ถ€ํŒŒ์ผ ์ถ”์ถœ
  โ”œโ”€ DKIM/SPF ๊ฒ€์ฆ
  โ”œโ”€ ํ‚ค์›Œ๋“œ ์ถ”์ถœ (textBody ์šฐ์„ , htmlBody ํด๋ฐฑ)
  โ”œโ”€ DB ์—…๋ฐ์ดํŠธ (ํŒŒ์‹ฑ ๊ฒฐ๊ณผ ์ €์žฅ)
  โ”œโ”€ ์ฒจ๋ถ€ํŒŒ์ผ ๋””์Šคํฌ ์ €์žฅ
  โ”‚
  โ–ผ
๋ถ„๋ฅ˜ (classifier)
  โ”‚
  โ”œโ”€ ํ…Œ๋„ŒํŠธ ์ „์šฉ ๋ฃฐ + ๊ธ€๋กœ๋ฒŒ ๋ฃฐ ์กฐํ•ฉ
  โ”œโ”€ ์šฐ์„ ์ˆœ์œ„ ์ˆœ์œผ๋กœ ๋ฃฐ ๋งค์นญ
  โ”œโ”€ ์กฐ๊ฑด: subject, from, to, cc, body, header
  โ”œโ”€ ์—ฐ์‚ฐ์ž: contains, equals, startsWith, endsWith, regex, notContains
  โ”‚
  โ–ผ
์•ก์…˜ ์‹คํ–‰
  โ”œโ”€ forward  โ†’ ์ง€์ • ์ฃผ์†Œ๋กœ ๋ฉ”์ผ ์ „๋‹ฌ, ์ „๋‹ฌ ๋กœ๊ทธ ๊ธฐ๋ก
  โ”œโ”€ log      โ†’ ๋‚ด๋ถ€ ๋กœ๊ทธ๋งŒ ๋‚จ๊น€
  โ”œโ”€ archive  โ†’ ๋ณด๊ด€ ์ฒ˜๋ฆฌ
  โ””โ”€ reject   โ†’ ๊ฑฐ๋ถ€ ์ฒ˜๋ฆฌ

๋ฃฐ ์•ก์…˜ ์ƒ์„ธ:

์•ก์…˜ ์„ค๋ช… ์ด๋ฉ”์ผ ์ƒํƒœ ๋ณ€๊ฒฝ
forward forwardTo์— ์ง€์ •๋œ ์ฃผ์†Œ๋กœ ๋ฉ”์ผ์„ SMTP ๋ฆด๋ ˆ์ด๋ฅผ ํ†ตํ•ด ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์ „๋‹ฌ ๊ฒฐ๊ณผ๋Š” ForwardLog์— ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค. received โ†’ forwarded (์„ฑ๊ณต ์‹œ) ๋˜๋Š” failed (์‹คํŒจ ์‹œ)
log ๋ณ„๋„ ์•ก์…˜ ์—†์ด ๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ๋งŒ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. ๋ฉ”์ผ์€ DB์— ๋ณด์กด๋ฉ๋‹ˆ๋‹ค. received โ†’ classified
archive ๋ฉ”์ผ์„ ๋ณด๊ด€ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ํ–ฅํ›„ ์กฐํšŒ๋Š” ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ์ „๋‹ฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. received โ†’ archived
reject ๋ฉ”์ผ์„ ๊ฑฐ๋ถ€ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. SMTP ์„ธ์…˜์—์„œ ๊ฑฐ๋ถ€ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. received โ†’ classified

์ƒ์„ธ: Email ์ƒํƒœ ๊ฐ’ (status)

์•„๋ž˜๋Š” DB์— ์ €์žฅ๋˜๋Š” Email.status ๊ฐ’๋“ค์˜ ์˜๋ฏธ์™€ ์ „ํ™˜ ์‹œ์ ์ž…๋‹ˆ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด๋ถ€ ๋กœ์ง๊ณผ ์ „๋‹ฌ/๋ถ„๋ฅ˜ ํ๋ฆ„์—์„œ ๋‹ค์Œ ๊ทœ์น™์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.

์ƒํƒœ ์˜๋ฏธ ์ „ํ™˜ ์กฐ๊ฑด / ์„ค์ • ์ฃผ์ฒด
received SMTP๋กœ ๋ฉ”์ผ์„ ์ˆ˜์‹ ํ•˜์—ฌ ์ตœ์†Œ ๋ฐ์ดํ„ฐ(envelope + rawMessage)๋งŒ DB์— ์ €์žฅ๋œ ์ดˆ๊ธฐ ์ƒํƒœ. ์›Œ์ปค๊ฐ€ ์•„์ง ํŒŒ์‹ฑ/๋ถ„๋ฅ˜/์ „๋‹ฌ์„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์€ ์ƒํƒœ์ž…๋‹ˆ๋‹ค. SMTP ์„œ๋ฒ„๊ฐ€ ๋ฉ”์ผ์„ ์ˆ˜์‹ ํ•˜๊ณ  envelope ์ •๋ณด์™€ rawMessage๋ฅผ DB์— ์ €์žฅํ•œ ์งํ›„, BullMQ ํ์— ๋“ฑ๋กํ•  ๋•Œ ๊ธฐ๋ณธ์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
classified ๋ถ„๋ฅ˜ ๋ฃฐ์— ๋งค์นญ๋˜์–ด ์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์ง€์ •๋œ ์ƒํƒœ. ๋ณดํ†ต ์•ก์…˜์ด log ๋˜๋Š” reject์ธ ๊ฒฝ์šฐ์— ์„ค์ •๋ฉ๋‹ˆ๋‹ค. ๋ถ„๋ฅ˜ ์—”์ง„์ด ๋ฃฐ์„ ๋งค์นญํ•˜๊ณ , ๋ฃฐ์˜ action์ด log ๋˜๋Š” reject์ผ ๋•Œ ๋ถ„๋ฅ˜ ์ฝ”๋“œ๊ฐ€ ์ƒํƒœ๋ฅผ classified๋กœ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
forwarded ๋ถ„๋ฅ˜ ๋ฃฐ์— ๋”ฐ๋ผ ์ง€์ •๋œ ์ฃผ์†Œ๋กœ SMTP ๋ฆด๋ ˆ์ด๋ฅผ ํ†ตํ•ด ์ „๋‹ฌ์ด ์™„๋ฃŒ๋œ ์ƒํƒœ. ๋ถ„๋ฅ˜ ์—”์ง„์ด forward ์•ก์…˜์„ ์„ ํƒํ•˜๊ณ , forwarder๊ฐ€ SMTP ๋ฆด๋ ˆ์ด์—์„œ ์„ฑ๊ณต ์‘๋‹ต์„ ๋ฐ›์•˜์„ ๋•Œ ForwardLog์™€ Email.status๋ฅผ forwarded๋กœ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
failed ๋ฉ”์ผ ์ „๋‹ฌ ๋˜๋Š” ํฌ์›Œ๋”ฉ ์‹œ๋„ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ์ƒํƒœ. forwardTo๊ฐ€ ์—†๊ฑฐ๋‚˜ SMTP ์ „๋‹ฌ ์‹คํŒจ ์‹œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. forward ์•ก์…˜ ์‹คํ–‰ ์ค‘ forwardTo๊ฐ€ ๋น„์–ด์žˆ๊ฑฐ๋‚˜, SMTP ๋ฆด๋ ˆ์ด์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด forwarder๊ฐ€ ์ƒํƒœ๋ฅผ failed๋กœ ์„ค์ •ํ•˜๊ณ  ForwardLog.error๋ฅผ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.
archived ๋ถ„๋ฅ˜ ๋ฃฐ์— ์˜ํ•ด ๋ณด๊ด€ ์ฒ˜๋ฆฌ๋œ ์ƒํƒœ. ์กฐํšŒ๋Š” ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ์ „๋‹ฌ์€ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ถ„๋ฅ˜ ์—”์ง„์ด archive ์•ก์…˜์„ ์„ ํƒํ•˜๋ฉด Email.status๋ฅผ archived๋กœ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.

์ƒ์„ธ: RuleAction ๊ฐ’๋“ค (action)

๊ฐ ์•ก์…˜์ด ์‹ค์ œ๋กœ ์ˆ˜ํ–‰ํ•˜๋Š” ๋™์ž‘๊ณผ ๊ทธ์— ๋”ฐ๋ฅธ ์ด๋ฉ”์ผ ์ƒํƒœ ๋ณ€๊ฒฝ์„ ๋ช…ํ™•ํžˆ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์•ก์…˜ ์‹คํ–‰ ๋™์ž‘ ์ด๋ฉ”์ผ ์ƒํƒœ ๋ณ€๊ฒฝ ๋ฐ ๋กœ๊ทธ
forward forwardTo์— ์ง€์ •๋œ ์ฃผ์†Œ๋กœ ๋ฉ”์ผ์„ SMTP ๋ฆด๋ ˆ์ด๋ฅผ ํ†ตํ•ด ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์ „๋‹ฌ ์‹œ ์›๋ณธ ๋ฉ”์‹œ์ง€์™€ ํ•„์š”ํ•œ ํ—ค๋”๋ฅผ ํฌํ•จํ•ด ์ „์†กํ•ฉ๋‹ˆ๋‹ค. ์ „๋‹ฌ ์‹œ๋„๋Š” ForwardLog ๋ ˆ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋ฉฐ, ์„ฑ๊ณต ์‹œ ForwardLog.status=success์™€ smtpResponse๋ฅผ ์ €์žฅํ•˜๊ณ  Email.status=forwarded๋กœ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. ์‹คํŒจ ์‹œ ForwardLog.status=failed, error ํ•„๋“œ์— ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ์ €์žฅํ•˜๊ณ  Email.status=failed๋กœ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
log ๋ณ„๋„์˜ ์™ธ๋ถ€ ์ „๋‹ฌ ์—†์ด ๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ๋งŒ ์‹œ์Šคํ…œ ๋กœ๊ทธ์™€ DB์— ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. Email.status๋ฅผ classified๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ForwardLog๋Š” ์ƒ์„ฑ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
archive ๋ฉ”์ผ์„ ๋‚ด๋ถ€ ์ €์žฅ์†Œ์— ๋ณด๊ด€ ์ฒ˜๋ฆฌํ•˜์—ฌ ๊ฒ€์ƒ‰/์กฐํšŒ๋Š” ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ „๋‹ฌ์€ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Email.status๋ฅผ archived๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๋ณด๊ด€ ๊ด€๋ จ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋Š” Tenant.settings ๋˜๋Š” ๋ณ„๋„ ๋ณด๊ด€ ํ…Œ์ด๋ธ”์— ์ €์žฅ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
reject ๋ฉ”์ผ์„ ๊ฑฐ๋ถ€ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. SMTP ์„ธ์…˜ ๋‹จ๊ณ„์—์„œ ๊ฑฐ๋ถ€ ์‘๋‹ต(5xx)์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํŠธ๋ฆฌ๊ฑฐํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ ๊ตฌํ˜„์—์„œ๋Š” ๊ฑฐ๋ถ€๋ฅผ ๋ณ„๋„ ์ƒํƒœ๋กœ ๋‘์ง€ ์•Š๊ณ  archived๋กœ ์„ค์ •ํ•˜๋ฉฐ, ๊ฑฐ๋ถ€ ๋กœ๊ทธ๋ฅผ ๋‚จ๊น๋‹ˆ๋‹ค. (์ถ”ํ›„ ๋ณ„๋„ rejected ์ƒํƒœ ๋„์ž… ๊ฐ€๋Šฅ)

์ƒ์„ธ: ConditionOperator ๊ฐ’๋“ค

์กฐ๊ฑด ์—ฐ์‚ฐ์ž๋Š” ํ•„๋“œ ๊ฐ’๊ณผ ๋น„๊ตํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ๋Œ€์†Œ๋ฌธ์ž๋ฅผ ๊ตฌ๋ถ„ํ•˜์ง€ ์•Š์œผ๋ฉฐ, regex๋Š” case-insensitive ํ”Œ๋ž˜๊ทธ๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค. ์œ ํšจํ•˜์ง€ ์•Š์€ ์ •๊ทœ์‹์€ ๊ฒฝ๊ณ  ๋กœ๊ทธ ํ›„ ํ•ด๋‹น ์กฐ๊ฑด์€ false๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

์—ฐ์‚ฐ์ž ๋™์ž‘ ๋ฐฉ์‹ ์ถ”๊ฐ€ ๋ฉ”๋ชจ
contains ๋Œ€์†Œ๋ฌธ์ž ๋ฌด์‹œ, ๋Œ€์ƒ ํ•„๋“œ์— ์ง€์ • ๋ฌธ์ž์—ด์ด ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฉด ๋งค์นญ ๋‚ด๋ถ€์ ์œผ๋กœ both values๋ฅผ ์†Œ๋ฌธ์žํ™”ํ•ด ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.
notContains ๋Œ€์†Œ๋ฌธ์ž ๋ฌด์‹œ, ๋Œ€์ƒ ํ•„๋“œ์— ์ง€์ • ๋ฌธ์ž์—ด์ด ํฌํ•จ๋˜์–ด ์žˆ์ง€ ์•Š์œผ๋ฉด ๋งค์นญ contains์˜ ์—ญ ๋…ผ๋ฆฌ์ž…๋‹ˆ๋‹ค.
equals ๋Œ€์†Œ๋ฌธ์ž ๋ฌด์‹œ, ๋Œ€์ƒ ํ•„๋“œ๊ฐ€ ์ง€์ • ๋ฌธ์ž์—ด๊ณผ ์ •ํ™•ํžˆ ์ผ์น˜ํ•˜๋ฉด ๋งค์นญ ๊ณต๋ฐฑ ๋ฐ ์ „์ฒด ๋ฌธ์ž์—ด ์ผ์น˜ ๊ธฐ์ค€์ž…๋‹ˆ๋‹ค. ํŠธ๋ฆฌ๋ฐ์€ ์ ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
startsWith ๋Œ€์†Œ๋ฌธ์ž ๋ฌด์‹œ, ๋Œ€์ƒ ํ•„๋“œ๊ฐ€ ์ง€์ • ๋ฌธ์ž์—ด๋กœ ์‹œ์ž‘ํ•˜๋ฉด ๋งค์นญ ์ ‘๋‘์‚ฌ ๊ฒ€์‚ฌ์ž…๋‹ˆ๋‹ค.
endsWith ๋Œ€์†Œ๋ฌธ์ž ๋ฌด์‹œ, ๋Œ€์ƒ ํ•„๋“œ๊ฐ€ ์ง€์ • ๋ฌธ์ž์—ด๋กœ ๋๋‚˜๋ฉด ๋งค์นญ ์ ‘๋ฏธ์‚ฌ ๊ฒ€์‚ฌ์ž…๋‹ˆ๋‹ค.
regex ์ •๊ทœํ‘œํ˜„์‹ ๋งค์นญ. ๋Œ€์†Œ๋ฌธ์ž ๋ฌด์‹œ ํ”Œ๋ž˜๊ทธ(i)๋ฅผ ์ ์šฉํ•˜์—ฌ ๋น„๊ต. ์ „๋‹ฌ๋œ ํŒจํ„ด์— ๋Œ€ํ•ด new RegExp(pattern, 'i')๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ํŒจํ„ด์ด ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ๊ฒฝ๊ณ  ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๊ณ  ํ•ด๋‹น ์กฐ๊ฑด์€ false๋กœ ํ‰๊ฐ€๋ฉ๋‹ˆ๋‹ค. ์บก์ฒ˜ ๊ทธ๋ฃน์€ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ์„ฑ๋Šฅ ์˜ํ–ฅ์— ์œ ์˜ํ•˜์„ธ์š”.

์ƒ์„ธ: ConditionField ๊ฐ’๋“ค (field)

๊ฐ ํ•„๋“œ๊ฐ€ ๋ฉ”์ผ์˜ ์–ด๋””์—์„œ ์ถ”์ถœ๋˜๋Š”์ง€ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

ํ•„๋“œ ์ถ”์ถœ ์œ„์น˜ ๋ฐ ํ˜•์‹
subject ์ด๋ฉ”์ผ์˜ Subject ํ—ค๋”์—์„œ ์ถ”์ถœ๋œ ๋ฌธ์ž์—ด. ํŒŒ์‹ฑ ์‹œ ์ธ์ฝ”๋”ฉ์„ ๋””์ฝ”๋”ฉํ•œ ํ›„ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
from From ํ—ค๋”์˜ ๊ฐ’, ํ‘œ์‹œ ์ด๋ฆ„๊ณผ ์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ํฌํ•จํ•œ ํ˜•์‹์œผ๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ: "Alice alice@example.com". ๋น„๊ต ์‹œ ์ „์ฒด ํ—ค๋” ๋ฌธ์ž์—ด์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
to To ํ—ค๋”์—์„œ ์ถ”์ถœ๋œ ์ˆ˜์‹ ์ž ๋ชฉ๋ก ๋ฌธ์ž์—ด. ๋‹ค์ค‘ ์ˆ˜์‹ ์ž๋Š” comma-separated ํ˜•์‹์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.
cc CC ํ—ค๋”์—์„œ ์ถ”์ถœ๋œ ์ฐธ์กฐ์ž ๋ชฉ๋ก ๋ฌธ์ž์—ด. ๋‹ค์ค‘ ์ฐธ์กฐ์ž๋Š” comma-separated ํ˜•์‹์ž…๋‹ˆ๋‹ค.
body ๋ณธ๋ฌธ ๋‚ด์šฉ. ์šฐ์„  textBody๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ, textBody๊ฐ€ ์—†์œผ๋ฉด htmlBody๋ฅผ ํ”Œ๋ฐฑ์œผ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. HTML์„ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ํƒœ๊ทธ๋ฅผ ์ œ๊ฑฐํ•œ ํ…์ŠคํŠธ๋กœ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.
header ์ž„์˜ ํ—ค๋”๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. ์กฐ๊ฑด์— headerName ํ•„๋“œ๋ฅผ ํ•จ๊ป˜ ์ œ๊ณตํ•ด์•ผ ํ•˜๋ฉฐ, ๋‚ด๋ถ€์ ์œผ๋กœ ํ—ค๋” ์ด๋ฆ„์„ ์†Œ๋ฌธ์ž๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: headerName: "x-priority".

์ƒ์„ธ: ForwardLog status ๊ฐ’

ForwardLog๋Š” ๋ฉ”์ผ ํฌ์›Œ๋”ฉ ์‹œ ์ „๋‹ฌ ์‹œ๋„์™€ ๊ฒฐ๊ณผ๋ฅผ ๋‚จ๊น๋‹ˆ๋‹ค.

์ƒํƒœ ์˜๋ฏธ ๊ธฐ๋ก ํ•„๋“œ
pending ์ „๋‹ฌ์ด ์•„์ง ์‹œ๋„๋˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜ ์‹œ๋„ ๋Œ€๊ธฐ ์ค‘์ธ ์ดˆ๊ธฐ ์ƒํƒœ DB ์ƒ์„ฑ ์‹œ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค.
success SMTP ๋ฆด๋ ˆ์ด๋ฅผ ํ†ตํ•ด ๋Œ€์ƒ์— ์ •์ƒ ์ „๋‹ฌ๋˜์–ด ์„œ๋ฒ„์˜ ์‘๋‹ต ์›๋ฌธ์ด ์ €์žฅ๋œ ์ƒํƒœ smtpResponse ํ•„๋“œ์— SMTP ์‘๋‹ต ์›๋ฌธ์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
failed ์ „๋‹ฌ ์‹œ๋„ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ์ƒํƒœ error ํ•„๋“œ์— ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ, attempts์— ์‹œ๋„ ํšŸ์ˆ˜๋ฅผ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.

TenantSettings JSON ๊ตฌ์กฐ

ํ…Œ๋„ŒํŠธ๋ณ„ ์„ค์ •์€ Tenant.settings ํ•„๋“œ์— JSON ๋ฌธ์ž์—ด๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ ์‚ฌ์šฉ๋˜๋Š” ์„ค์ • ์˜ˆ์‹œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

{
  "maxMessageSizeBytes": 10485760
}

์„ค๋ช…:

  • maxMessageSizeBytes: ์ด ํ…Œ๋„ŒํŠธ์—์„œ ํ—ˆ์šฉํ•˜๋Š” ์ตœ๋Œ€ ๋ฉ”์‹œ์ง€ ํฌ๊ธฐ(๋ฐ”์ดํŠธ). ์œ„ ์˜ˆ์‹œ๋Š” 10MB์ž…๋‹ˆ๋‹ค. ํ…Œ๋„ŒํŠธ ์„ค์ •์ด ๋น„์–ด ์žˆ์œผ๋ฉด ๊ธ€๋กœ๋ฒŒ ํ™˜๊ฒฝ๋ณ€์ˆ˜ MAX_MESSAGE_SIZE_MB๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ํ—ˆ์šฉ ํฌ๊ธฐ๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.

ํ™˜๊ฒฝ๋ณ€์ˆ˜: MAX_MESSAGE_SIZE_MB

์ด๋ฏธ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ํ‘œ์— MAX_MESSAGE_SIZE_MB๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋™์ž‘๊ณผ ์šฐ์„ ์ˆœ์œ„๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • MAX_MESSAGE_SIZE_MB(ํ™˜๊ฒฝ๋ณ€์ˆ˜): ๊ธ€๋กœ๋ฒŒ ๊ธฐ๋ณธ ์ˆ˜์‹  ๋ฉ”์‹œ์ง€ ํฌ๊ธฐ ์ œํ•œ(MB ๋‹จ์œ„). ์˜ˆ: 25๋Š” 25MB๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
  • ํ…Œ๋„ŒํŠธ๋ณ„๋กœ Tenant.settings.maxMessageSizeBytes๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด ํ•ด๋‹น ๊ฐ’์ด ๊ธ€๋กœ๋ฒŒ ๊ฐ’์„ ๋ฎ์–ด์”๋‹ˆ๋‹ค. ํ…Œ๋„ŒํŠธ ์„ค์ • ๊ฐ’์€ ๋ฐ”์ดํŠธ ๋‹จ์œ„์ž…๋‹ˆ๋‹ค.

์šฐ์„ ์ˆœ์œ„: Tenant.settings.maxMessageSizeBytes (์žˆ์œผ๋ฉด ์‚ฌ์šฉ) โ†’ ํ™˜๊ฒฝ๋ณ€์ˆ˜ MAX_MESSAGE_SIZE_MB โ†’ ์ฝ”๋“œ ๋‚ด ๊ธฐ๋ณธ๊ฐ’(25MB).

๋ถ„๋ฅ˜ ๋ฃฐ

DB์˜ ClassificationRule ํ…Œ์ด๋ธ”์— ์ €์žฅ๋˜๋ฉฐ JSON ํ˜•์‹์˜ ์กฐ๊ฑด ๋ฐฐ์—ด๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.

์กฐ๊ฑด ๊ตฌ์กฐ

{
  "field": "subject",
  "operator": "contains",
  "value": "๊ธด๊ธ‰"
}

์ง€์› ํ•„๋“œ

ํ•„๋“œ ์„ค๋ช… ์˜ˆ์‹œ ๊ฐ’
subject ๋ฉ”์ผ ์ œ๋ชฉ ๊ธด๊ธ‰, [์•Œ๋ฆผ]
from ๋ฐœ์‹ ์ž (From ํ—ค๋”) admin@example.com
to ์ˆ˜์‹ ์ž (To ํ—ค๋”) support@example.com
cc ์ฐธ์กฐ (CC ํ—ค๋”) manager@example.com
body ๋ณธ๋ฌธ ๋‚ด์šฉ (text ์šฐ์„ , html ํด๋ฐฑ) ๊ฒฐ์ œ ์™„๋ฃŒ, ์Šน์ธ ์š”์ฒญ
header ์ž„์˜ ํ—ค๋” (headerName ํ•„๋“œ๋กœ ํ—ค๋” ์ด๋ฆ„ ์ง€์ •) X-Priority: 1

์ง€์› ์—ฐ์‚ฐ์ž

์—ฐ์‚ฐ์ž ์„ค๋ช… ๋™์ž‘ ์ƒ์„ธ
contains ํฌํ•จ ์—ฌ๋ถ€ ๋Œ€์†Œ๋ฌธ์ž ๋ฌด์‹œ. ํ•„๋“œ ๊ฐ’์— ์ง€์ • ๋ฌธ์ž์—ด์ด ํฌํ•จ๋˜๋ฉด ๋งค์นญ
notContains ๋ฏธํฌํ•จ ์—ฌ๋ถ€ ๋Œ€์†Œ๋ฌธ์ž ๋ฌด์‹œ. ํ•„๋“œ ๊ฐ’์— ์ง€์ • ๋ฌธ์ž์—ด์ด ํฌํ•จ๋˜์ง€ ์•Š์œผ๋ฉด ๋งค์นญ
equals ์ •ํ™•ํžˆ ์ผ์น˜ ๋Œ€์†Œ๋ฌธ์ž ๋ฌด์‹œ. ํ•„๋“œ ๊ฐ’์ด ์ง€์ • ๋ฌธ์ž์—ด๊ณผ ์™„์ „ํžˆ ๋™์ผํ•˜๋ฉด ๋งค์นญ
startsWith ์ ‘๋‘์‚ฌ ์ผ์น˜ ๋Œ€์†Œ๋ฌธ์ž ๋ฌด์‹œ. ํ•„๋“œ ๊ฐ’์ด ์ง€์ • ๋ฌธ์ž์—ด๋กœ ์‹œ์ž‘ํ•˜๋ฉด ๋งค์นญ
endsWith ์ ‘๋ฏธ์‚ฌ ์ผ์น˜ ๋Œ€์†Œ๋ฌธ์ž ๋ฌด์‹œ. ํ•„๋“œ ๊ฐ’์ด ์ง€์ • ๋ฌธ์ž์—ด๋กœ ๋๋‚˜๋ฉด ๋งค์นญ
regex ์ •๊ทœํ‘œํ˜„์‹ ๋งค์นญ ๋Œ€์†Œ๋ฌธ์ž ๋ฌด์‹œ ํ”Œ๋ž˜๊ทธ(i) ์ ์šฉ. ์œ ํšจํ•˜์ง€ ์•Š์€ ์ •๊ทœ์‹์€ ๋ฌด์‹œ๋จ

๋ฃฐ ์˜ˆ์‹œ

"์ œ๋ชฉ์— '๊ธด๊ธ‰'์ด ํฌํ•จ๋œ ๋ฉ”์ผ์„ opsํŒ€์— ์ „๋‹ฌ":

{
  "name": "urgent-to-ops",
  "priority": 100,
  "conditions": [
    { "field": "subject", "operator": "contains", "value": "๊ธด๊ธ‰" }
  ],
  "action": "forward",
  "forwardTo": "ops@example.com",
  "category": "urgent"
}

"ํŠน์ • ๋„๋ฉ”์ธ์—์„œ ์˜จ ๋ฉ”์ผ์„ ๋ณด๊ด€ ์ฒ˜๋ฆฌ":

{
  "name": "archive-newsletters",
  "priority": 10,
  "conditions": [
    { "field": "from", "operator": "endsWith", "value": "@newsletter.example.com" }
  ],
  "action": "archive",
  "category": "newsletter"
}

๋ถ„๋ฅ˜ ๋ฃฐ OR ์กฐ๊ฑด (matchMode)

ClassificationRule์— matchMode ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์กฐ๊ฑด ๋งค์นญ ๋ฐฉ์‹์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๊ธฐ๋ณธ๊ฐ’: all โ€” ๋ชจ๋“  ์กฐ๊ฑด์ด ์ถฉ์กฑ๋˜์–ด์•ผ ๋ฃฐ์ด ๋งค์นญ๋ฉ๋‹ˆ๋‹ค (AND)
  • any: ํ•˜๋‚˜์˜ ์กฐ๊ฑด๋งŒ ์ถฉ์กฑ๋˜์–ด๋„ ๋ฃฐ์ด ๋งค์นญ๋ฉ๋‹ˆ๋‹ค (OR)
  • API์—์„œ ๋ฃฐ ์ƒ์„ฑ/์ˆ˜์ • ์‹œ matchMode ํ•„๋“œ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

์˜ˆ์‹œ:

{
  "name": "notify-if-from-or-subject",
  "priority": 50,
  "matchMode": "any",
  "conditions": [
    { "field": "from", "operator": "contains", "value": "alerts@example.com" },
    { "field": "subject", "operator": "contains", "value": "์ค‘์š”" }
  ],
  "action": "forward",
  "forwardTo": "ops@example.com"
}

DB ์ „ํ™˜ ๊ฐ€์ด๋“œ

ํ˜„์žฌ SQLite๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์œผ๋ฉฐ, PostgreSQL ๋˜๋Š” MySQL๋กœ ์ „ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

1. prisma/schema.prisma ์ˆ˜์ •

datasource db {
  provider = "postgresql"   // "mysql"๋„ ๊ฐ€๋Šฅ
  url      = env("DATABASE_URL")
}

2. .env ์ˆ˜์ •

# PostgreSQL
DATABASE_URL="postgresql://user:password@localhost:5432/mail_gateway"

# MySQL
DATABASE_URL="mysql://user:password@localhost:3306/mail_gateway"

3. ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์žฌ์ƒ์„ฑ

# ๊ธฐ์กด migrations ๋””๋ ‰ํ† ๋ฆฌ ์‚ญ์ œ ํ›„
rm -rf prisma/migrations

# ์ƒˆ DB์— ๋งž๋Š” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ƒ์„ฑ
npm run db:migrate

์ „๋‹ฌ ์žฌ์‹œ๋„ ๋ฉ”์ปค๋‹ˆ์ฆ˜

์ „๋‹ฌ ์‹คํŒจ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ BullMQ์˜ ๋‚ด์žฅ ์žฌ์‹œ๋„ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ํ†ตํ•ด ์ž๋™ ์žฌ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.

  • ์žฌ์‹œ๋„ ๋ฐฉ์‹: BullMQ ๋‚ด์žฅ exponential backoff
    • ์ง€์—ฐ ๊ณ„์‚ฐ: delay = 60,000ms ร— 2^(attempt-1) (1๋ถ„, 2๋ถ„, 4๋ถ„, 8๋ถ„, 16๋ถ„)
    • ์ตœ๋Œ€ 5ํšŒ ์žฌ์‹œ๋„ ํ›„ ์ตœ์ข… ์‹คํŒจ ์ฒ˜๋ฆฌ
  • ๋ฉฑ๋“ฑ์„ฑ ๋ณด์žฅ: emailId๋ฅผ BullMQ jobId๋กœ ์‚ฌ์šฉํ•˜์—ฌ ๋™์ผ ์ด๋ฉ”์ผ์˜ ์ค‘๋ณต ์ž‘์—…์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค
  • DB ์ฒดํฌํฌ์ธํŠธ: ์›Œ์ปค๊ฐ€ ์ž‘์—… ์žฌ์‹œ๋„ ์‹œ ์ด๋ฏธ ์™„๋ฃŒ๋œ ๋‹จ๊ณ„(ํŒŒ์‹ฑ, ์ฒจ๋ถ€ํŒŒ์ผ ์ €์žฅ ๋“ฑ)๋Š” ๊ฑด๋„ˆ๋›ฐ์–ด ๋ถˆํ•„์š”ํ•œ ์ค‘๋ณต ์ž‘์—…์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค
  • ์ˆ˜๋™ ์žฌ์‹œ๋„: POST /api/emails/:id/retry ์—”๋“œํฌ์ธํŠธ๋กœ ํŠน์ • ์ด๋ฉ”์ผ์„ ์ฆ‰์‹œ ์žฌ์‹œ๋„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์ƒˆ BullMQ ์ž‘์—…์œผ๋กœ ๋“ฑ๋ก)

ํ‚ค์›Œ๋“œ ์ถ”์ถœ / ์ž๋™ ํƒœ๊น…

์ˆ˜์‹ ๋œ ๋ฉ”์ผ์˜ ๋ณธ๋ฌธ์—์„œ ์ž๋™์œผ๋กœ ํ‚ค์›Œ๋“œ๋ฅผ ์ถ”์ถœํ•˜์—ฌ Email ๋ ˆ์ฝ”๋“œ์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

  • ์†Œ์Šค: textBody ์šฐ์„ , textBody๊ฐ€ ์—†์œผ๋ฉด htmlBody๋ฅผ ํ”Œ๋ฐฑ์œผ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. HTML์€ ํƒœ๊ทธ ์ œ๊ฑฐ ํ›„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค
  • ์•Œ๊ณ ๋ฆฌ์ฆ˜: TF(Term Frequency) ๊ธฐ๋ฐ˜์œผ๋กœ ์ƒ์œ„ 10๊ฐœ ํ‚ค์›Œ๋“œ๋ฅผ ์„ ์ •ํ•ฉ๋‹ˆ๋‹ค
  • ์–ธ์–ด: ์˜๋ฌธ๊ณผ ํ•œ๊ธ€์„ ๋ชจ๋‘ ์ง€์›ํ•˜๋ฉฐ, ๋ถˆ์šฉ์–ด(stopwords)๋ฅผ ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค
  • ๊ฒฐ๊ณผ ์ €์žฅ: Email.keywords ํ•„๋“œ์— JSON ๋ฐฐ์—ด ๋ฌธ์ž์—ด๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค

DKIM / SPF ๊ฒ€์ฆ

์„ ํƒ์  ๊ธฐ๋Šฅ์œผ๋กœ, mailauth ํŒจํ‚ค์ง€ ์„ค์น˜ ์‹œ DKIM/SPF ๊ฒ€์ฆ์ด ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค. ์„ค์น˜๊ฐ€ ์—†์œผ๋ฉด graceful fallback์œผ๋กœ ๊ฒ€์ฆ์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ณ  none ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

  • ํ™œ์„ฑํ™” ๋ฐฉ๋ฒ•: npm install mailauth ํ›„ ์„œ๋ฒ„ ์žฌ์‹œ์ž‘
  • DKIM ๊ฒฐ๊ณผ: pass, fail, none, error
  • SPF ๊ฒฐ๊ณผ: pass, fail, none, softfail, error
  • ๊ฒฐ๊ณผ ์ €์žฅ: Email.dkimResult, Email.spfResult ํ•„๋“œ์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค

DB ์Šคํ‚ค๋งˆ

Email

์ˆ˜์‹ ๋œ ๋ชจ๋“  ๋ฉ”์ผ์˜ ์›๋ณธ ๋ฐ ํŒŒ์‹ฑ ๊ฒฐ๊ณผ ์ €์žฅ.

์ปฌ๋Ÿผ ํƒ€์ž… ์„ค๋ช…
id UUID PK
mailFrom String SMTP MAIL FROM
rcptTo String SMTP RCPT TO
remoteIp String ๋ฐœ์‹  IP
messageId String? Message-ID ํ—ค๋” (unique)
subject String? ์ œ๋ชฉ
fromHeader String? From ํ—ค๋”
toHeader String? To ํ—ค๋”
ccHeader String? CC ํ—ค๋”
textBody String? ํ…์ŠคํŠธ ๋ณธ๋ฌธ
htmlBody String? HTML ๋ณธ๋ฌธ
rawMessage String RFC822 ์›๋ฌธ
status String received / classified / forwarded / failed / archived
category String? ๋ถ„๋ฅ˜ ์นดํ…Œ๊ณ ๋ฆฌ
matchedRule String? ๋งค์นญ๋œ ๋ฃฐ ์ด๋ฆ„
dkimResult String? DKIM ๊ฒ€์ฆ ๊ฒฐ๊ณผ (pass / fail / none / error)
spfResult String? SPF ๊ฒ€์ฆ ๊ฒฐ๊ณผ (pass / fail / none / softfail / error)
keywords String? ์ž๋™ ์ถ”์ถœ๋œ ํ‚ค์›Œ๋“œ (JSON ๋ฐฐ์—ด ๋ฌธ์ž์—ด)
tenantId String? ์†Œ์† ํ…Œ๋„ŒํŠธ ID

Email ์ƒํƒœ (status) ๊ฐ’:

์ƒํƒœ ์„ค๋ช…
received SMTP๋กœ ์ˆ˜์‹  ์™„๋ฃŒ. ์ตœ์†Œ ๋ฐ์ดํ„ฐ(envelope + rawMessage)๋งŒ DB ์ €์žฅ๋จ. ์›Œ์ปค์˜ ํŒŒ์‹ฑ/๋ถ„๋ฅ˜ ์ „
classified ์›Œ์ปค๊ฐ€ ํŒŒ์‹ฑ ํ›„ ๋ถ„๋ฅ˜ ๋ฃฐ์— ๋งค์นญํ•˜์—ฌ ์นดํ…Œ๊ณ ๋ฆฌ ์ง€์ •๋จ (์•ก์…˜์ด log์ธ ๊ฒฝ์šฐ)
forwarded ์›Œ์ปค๊ฐ€ ๋ถ„๋ฅ˜ ๋ฃฐ์— ๋”ฐ๋ผ ์ง€์ • ์ฃผ์†Œ๋กœ ์ „๋‹ฌ ์™„๋ฃŒ
failed ์›Œ์ปค์˜ ๋ฉ”์ผ ์ „๋‹ฌ ์‹œ๋„ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ
archived ์›Œ์ปค๊ฐ€ ๋ถ„๋ฅ˜ ๋ฃฐ์— ์˜ํ•ด ๋ณด๊ด€ ์ฒ˜๋ฆฌ๋จ

Attachment

์ปฌ๋Ÿผ ํƒ€์ž… ์„ค๋ช…
id UUID PK
emailId String ์†Œ์† ์ด๋ฉ”์ผ ID (FK โ†’ Email)
filename String? ์ฒจ๋ถ€ํŒŒ์ผ ์›๋ณธ ํŒŒ์ผ๋ช…
contentType String MIME ํƒ€์ž… (์˜ˆ: application/pdf, image/png)
size Int ํŒŒ์ผ ํฌ๊ธฐ (๋ฐ”์ดํŠธ)
checksum String? SHA-256 ํ•ด์‹œ (๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆ์šฉ)
storagePath String? ๋””์Šคํฌ ์ €์žฅ ๊ฒฝ๋กœ

ClassificationRule

์ปฌ๋Ÿผ ํƒ€์ž… ์„ค๋ช…
id UUID PK
name String ๋ฃฐ ์ด๋ฆ„ (unique)
description String? ๋ฃฐ ์„ค๋ช…
priority Int ์šฐ์„ ์ˆœ์œ„ (๋†’์„์ˆ˜๋ก ๋จผ์ € ํ‰๊ฐ€, ๊ธฐ๋ณธ๊ฐ’: 0)
enabled Boolean ํ™œ์„ฑํ™” ์—ฌ๋ถ€ (๊ธฐ๋ณธ๊ฐ’: true)
conditions String JSON ๋ฐฐ์—ด โ€” ๋งค์นญ ์กฐ๊ฑด ๋ชฉ๋ก
action String ๋งค์นญ ์‹œ ์‹คํ–‰ํ•  ์•ก์…˜: forward | log | archive | reject
forwardTo String? ์ „๋‹ฌ ๋Œ€์ƒ ์ด๋ฉ”์ผ ์ฃผ์†Œ (action์ด forward์ผ ๋•Œ ํ•„์ˆ˜)
category String? ๋ถ„๋ฅ˜ ์นดํ…Œ๊ณ ๋ฆฌ ํƒœ๊ทธ
matchMode String ์กฐ๊ฑด ๋งค์นญ ๋ชจ๋“œ: all (AND, ๊ธฐ๋ณธ๊ฐ’) | any (OR)
tenantId String? ์†Œ์† ํ…Œ๋„ŒํŠธ ID (FK โ†’ Tenant, null์ด๋ฉด ๊ธ€๋กœ๋ฒŒ ๋ฃฐ)

ForwardLog

์ปฌ๋Ÿผ ํƒ€์ž… ์„ค๋ช…
id UUID PK
emailId String ๋Œ€์ƒ ์ด๋ฉ”์ผ ID (FK โ†’ Email)
forwardTo String ์ „๋‹ฌ ๋Œ€์ƒ ์ด๋ฉ”์ผ ์ฃผ์†Œ
status String ์ „๋‹ฌ ์ƒํƒœ: pending (๋Œ€๊ธฐ) | success (์„ฑ๊ณต) | failed (์‹คํŒจ)
error String? ์‹คํŒจ ์‹œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€
smtpResponse String? SMTP ์„œ๋ฒ„ ์‘๋‹ต ์›๋ฌธ
attempts Int ์ „๋‹ฌ ์‹œ๋„ ํšŸ์ˆ˜ (๊ธฐ๋ณธ๊ฐ’: 1)
nextRetryAt DateTime? ๋‹ค์Œ ์žฌ์‹œ๋„ ์˜ˆ์ • ์‹œ๊ฐ

Tenant

๋ฉ€ํ‹ฐ ํ…Œ๋„ŒํŠธ ๊ฒฉ๋ฆฌ ๋‹จ์œ„.

์ปจ๋Ÿผ ํƒ€์ž… ์„ค๋ช…
id UUID PK
name String ํ…Œ๋„ŒํŠธ ์ด๋ฆ„ (unique)
domains String JSON ๋ฐฐ์—ด (๋„๋ฉ”์ธ ๋ชฉ๋ก)
settings String? JSON ์„ค์ •
enabled Boolean ํ™œ์„ฑํ™” ์—ฌ๋ถ€

Use Cases

์‹ค์ œ ์šด์˜ ์‹œ๋‚˜๋ฆฌ์˜ค๋ณ„ ๊ตฌ์„ฑ ๊ฐ€์ด๋“œ์ž…๋‹ˆ๋‹ค.

# ์‹œ๋‚˜๋ฆฌ์˜ค ์„ค๋ช…
01 ๊ณ ๊ฐ ์ง€์› ๋ฉ”์ผ ๋ผ์šฐํŒ… ์ˆ˜์‹  ๋ฉ”์ผ์„ ์ œ๋ชฉ/๋ฐœ์‹ ์ž ๊ธฐ๋ฐ˜์œผ๋กœ ๋‹ด๋‹น ๋ถ€์„œ์— ์ž๋™ ์ „๋‹ฌ
02 ๋ฉ€ํ‹ฐ ํ…Œ๋„ŒํŠธ SaaS ๋ฉ”์ผ ๊ฒฉ๋ฆฌ ๋„๋ฉ”์ธ๋ณ„ ํ…Œ๋„ŒํŠธ ๋ถ„๋ฆฌ, ํ…Œ๋„ŒํŠธ ์ „์šฉ ๋ฃฐ๋กœ ๋ฉ”์ผ ๊ฒฉ๋ฆฌ
03 ๋‚ด๋ถ€ ๋ฉ”์ผ ๊ฐ์‚ฌ ๋ฐ ์ปดํ”Œ๋ผ์ด์–ธ์Šค ๋ชจ๋“  ๋ฉ”์ผ์„ ์•„์นด์ด๋ธŒํ•˜๊ณ  ํ‚ค์›Œ๋“œ ๊ธฐ๋ฐ˜ ์ปดํ”Œ๋ผ์ด์–ธ์Šค ๊ฐ์‚ฌ
04 ๋ชจ๋‹ˆํ„ฐ๋ง ์•Œ๋ฆผ ์ง‘์•ฝ Grafana, Prometheus ๋“ฑ ์•Œ๋ฆผ ๋ฉ”์ผ์„ ๋ถ„๋ฅ˜ยท์ง‘์•ฝํ•˜์—ฌ ์˜จ์ฝœ ํŒ€์— ์ „๋‹ฌ
05 ๊ฐœ๋ฐœ/์Šคํ…Œ์ด์ง• ๋ฉ”์ผ ์บก์ฒ˜ ๋ฆด๋ ˆ์ด ์—†์ด ๋ฉ”์ผ ํŠธ๋žฉ์œผ๋กœ ์‚ฌ์šฉ, ๋Œ€์‹œ๋ณด๋“œ์—์„œ ๋ฐœ์†ก ๋ฉ”์ผ ๊ฒ€์‚ฌ
06 MX ๋ ˆ์ฝ”๋“œ ๊ธฐ๋ฐ˜ ํ”„๋กœ๋•์…˜ ๋ฉ”์ผ ์ˆ˜์‹  DNS MX โ†’ ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ โ†’ Mail Gateway, STARTTLS ํ”„๋กœ๋•์…˜ ๊ตฌ์„ฑ
07 ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ ๋ณตํ•ฉ ๋ผ์šฐํŒ… ๋ณธ์‚ฌยท์žํšŒ์‚ฌ ๋ฉ€ํ‹ฐ ํ…Œ๋„ŒํŠธ, ๋ณตํ•ฉ ๋ฃฐ ์ฒด์ธ, ๋Œ€๊ทœ๋ชจ ํŠธ๋ž˜ํ”ฝ ๋Œ€์‘

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages