Rinda 개발자 온보딩 가이드

수출 기업 매니저를 위한 AI 영업 자동화 플랫폼. 바이어 발굴 → 이메일 캠페인 → 답장 관리까지 자동화하는 풀스택 모노레포입니다. 이 문서 하나로 전체 아키텍처와 각 용어를 이해할 수 있습니다.

3
워크스페이스 (admin · server · e2e)
195+
API 라우트
110+
DB 테이블
60+
BullMQ 큐
핵심 멘탈 모델 한 문장: 사용자가 화면(admin)에서 요청 → API 서버(elysia-server)가 받아 routes → services → db로 처리하고, 오래 걸리는 일(이메일 발송·AI 검색)은 BullMQ 큐에 넣어 워커가 처리하며 진행률은 SSE로 실시간 전송한다.

빠른 시작

사전 요구사항: Node 20+, Bun 1.2+, Yarn 4 (corepack), Docker, just (작업 러너).

git clone git@github.com:FINGU-GRINDA/send-grid-test.git
cd send-grid-test

just setup     # 사전검증 + .env 생성 + Docker(PG18+Redis8) + 의존성 설치 + 스키마 push
just seed      # 시드 데이터 생성
just dev       # admin: http://localhost:5173  /  API: http://localhost:3001
패키지 매니저가 폴더마다 다릅니다. admin=yarn, elysia-server=bun, e2e=npm. 폴더를 헷갈리면 lockfile이 깨집니다.

모노레포 구조

send-grid-test/ ├── admin/ React 19 + Vite 프론트엔드 (yarn) ├── elysia-server/ Elysia + Bun API 서버 + 워커 (bun) ├── e2e/ Playwright E2E (헥사고날 포트/어댑터) (npm) ├── docs/ C4 아키텍처 다이어그램 + ADR + 규칙 ├── scripts/ · sh/ 배포 · DB · 유틸 스크립트 ├── .github/workflows/ CI/CD (alpha · beta · e2e) ├── docker-compose.*.yml 환경별(local/alpha/beta) 스택 ├── justfile 작업 러너 (setup · dev · seed · db-reset) ├── send-ci.sh 통합 검증 (lint + type-check + build) └── CLAUDE.md AI 어시스턴트 + 팀 개발 규칙
폴더역할패키지 매니저
admin/유저·관리자 대시보드 UIyarn 4
elysia-server/REST API + BullMQ 백그라운드 워커bun
e2e/Playwright 통합 테스트npm

기술 스택

프론트엔드

React 19 · Vite · Tailwind 4 · TanStack Query · Jotai · React Router 7 · Radix UI · i18next(4개국어)

백엔드

Elysia (Bun) · Drizzle ORM · BullMQ · Zod · Pino · 3-layer 아키텍처

인프라

PostgreSQL 18 · Redis 8 · Docker Compose · Nginx · AWS EC2 · Cloudflare · Infisical

AI / LLM

Gemini 3 Flash(기본) · Claude · OpenAI · LangGraph · Mastra agent framework

이메일

SendGrid(내부 도메인) · AWS SES(유저 도메인) · Nylas · Unipile

결제 · 외부

Paddle(글로벌) · Toss(한국) · HasData · Hunter.io · Slack · S3

SSOT 원칙 (Single Source of Truth)

같은 데이터는 한 곳에만 정의합니다. 중복되면 drift(불일치)가 생깁니다. 프론트는 백엔드의 미러이며 자체 SSOT를 만들지 않습니다.

데이터 성격저장 위치
영속·다중 사용자 공유 (유저·결제·엔티티)PostgreSQL (Drizzle schema)
휘발·짧은 TTL (캐시·세션·rate limit·큐)Redis / BullMQ
서버 빌드 상수 (라우트·모델·티어)elysia-server/src/** 모듈 export
프론트 빌드 상수 (디자인 토큰·enum)admin/src/** 모듈 export
서버 상태의 클라 캐시TanStack Query (읽기 전용, mutate 금지)
클라 ephemeral UI 상태 (모달·폼)Jotai

백엔드 · 3-Layer 아키텍처

모든 요청은 세 계층을 순서대로 통과합니다. 각 계층은 하나의 책임만 가집니다.

┌─────────────┐ HTTP 요청/응답만. 인증 매크로로 보호. 비즈니스 로직 없음. │ routes │ 예: routes/sequences.routes.ts └──────┬──────┘ ▼ ┌─────────────┐ 비즈니스 로직. 트랜잭션·검증·캐싱. Drizzle 쿼리 직접 사용. │ services │ 예: services/sequence-read.service.ts (561개 파일) └──────┬──────┘ ▼ ┌─────────────┐ Drizzle ORM 스키마 + 정규화된 테이블. │ db │ 예: db/schema/sequences.ts └─────────────┘

대표 흐름 예시 — 시퀀스 검색

// 1) routes/sequences.routes.ts  — 요청 파싱 + 서비스 위임 + ok() 응답
.get("/search", async ({ query, workspace }) => {
  const { items, cursor } = await listSequencesPaged({ ...filters })
  return ok({ items, cursor })            // utils/reply.ts 헬퍼
}, { workspaceAuth: true })               // ← 인증 매크로 (필수 선언)

// 2) services/sequence-read.service.ts  — DB 직접 접근
// 3) db/schema/sequences.ts             — 테이블 스키마
에러는 AppError 서브클래스(NotFoundError, BadRequestError 등)를 throw → 글로벌 핸들러가 JSON으로 변환. 응답은 항상 ok(data) / created(data) 헬퍼 사용 ({ success: true } 직접 작성 금지).

백엔드 · 인증 매크로

모든 라우트는 반드시 인증 매크로를 인라인 선언해야 합니다. 정적 게이트(check:route-auth)가 유일한 방어선이며, 런타임은 미선언 시 fail-open(public)이 됩니다. SSOT: plugins/auth.macro.ts

매크로검증주입
public: true없음 (공개)
auth: trueJWT 토큰만{ user }
adminAuth: trueJWT + DB활성 + role=admin{ user }
workspaceAuth: trueJWT + X-Workspace-Id + 멤버십{ user, workspace }
workspaceAuth: { resource, action }위 + IAM 정책 검사{ user, workspace }
pre-push 훅을 --no-verify로 우회하지 마세요. 라우트 권한 누락은 보안 사고로 직결됩니다. drift 자동보정: sh send-ci.sh auth --fix. (ADR-0001: default-deny)

백엔드 · DB · Drizzle

PostgreSQL 18 + Drizzle ORM. 스키마는 elysia-server/src/db/schema/, 마이그레이션은 drizzle/*.sql.

마이그레이션 워크플로

// 1) src/db/schema/*.ts 편집
// 2) 마이그레이션 자동 생성 (손으로 쓰지 말 것!)
bun db:generate     // → 0NNN_*.sql + meta/0NNN_snapshot.json + _journal.json
// 3) 세 파일 함께 git add (하나라도 빠지면 게이트 실패)
git add drizzle/
// 4) 무결성 검증 (husky pre-commit + CI 에서 자동)
bun check:migrations   // F1~F8 실패모드 차단
철칙 5가지
① 커밋된 drizzle/*.sql 수정 금지 (SHA-256 hash 깨짐) → hot-fix는 새 마이그레이션.
② 신규 PK는 UUIDv7: uuid().default(sql`uuidv7()`)gen_random_uuid() 금지 (PG18 native).
③ OFFSET 페이지네이션 금지 → keyset(cursor) 사용.
④ Enum 변경은 단독 마이그레이션으로 분리 (같은 트랜잭션 후속 DDL을 PG가 거부).
⑤ 무거운 집계(COUNT DISTINCT, DISTINCT JOIN)는 analyticsDb 사용 (work_mem 256MB).
DB 연결 분리: 기본 db(OLTP·모든 write, work_mem 32MB) vs analyticsDb(readonly 집계, 256MB). 무거운 쿼리를 기본 db로 돌리면 디스크 spill 발생.

백엔드 · 큐 · 워커 (BullMQ)

오래 걸리는 작업은 API에서 직접 처리하지 않고 BullMQ 큐에 넣습니다. 워커는 별도 Bun 프로세스로 독립 배포됩니다.

API (routes/services) Redis Worker (별도 프로세스) addSequenceEmailJob() ──────▶ [큐: SEQUENCE_EMAIL] ──────▶ sequence-email.worker.ts └─ 이메일 발송 → 진행률 publish
구성위치
큐 타입 정의 (60+ 큐)lib/queue/types.ts
큐 프로듀서 (작업 등록)lib/queue/queues.ts
워커 구현workers/bullmq/*.worker.ts
워커 엔트리포인트src/worker.ts · worker-buyersearch.ts

대표 큐

  • SEQUENCE_EMAIL — 시퀀스 이메일 발송 / SEQUENCE_EMAIL_LOADER — DB pending → BullMQ delayed 적재(30초 틱)
  • LEAD_DISCOVERY_SEARCH / _ANALYZE — 리드 발굴 검색·분석
  • BUYER_SEARCH_MASTRA — 바이어 탐색 AI 파이프라인 / AGENT_MISSION — AI 에이전트 실행
내장 retry(exponential backoff), rate limiting, FlowProducer(종속 작업 자동 활성화). 작업 실행 기록은 job-log.service.ts가 DB에 저장.

백엔드 · 실시간 진행률 (SSE)

장시간 잡(시퀀스 발송, 리드 발굴)의 진행률은 SSE + Redis Pub/Sub로 실시간 전송. SSOT는 항상 DB. 위치: lib/sse/

클라이언트 연결 → Connecting (메타 이벤트, /progress snapshot 으로 분모 확정) → Replaying (Last-Event-ID 이후 누락분만 replay) → Live (Redis Pub/Sub 구독 → 실시간 forward) → Cleanup (terminal 이벤트 or idle timeout) ⤷ 25초마다 heartbeat (nginx 60초 timeout 방어)
재연결 시 Last-Event-ID로 놓친 이벤트만 복구(ring buffer). 도메인별 어댑터(isTerminal, filterEvent, dedupeKey)로 동작을 커스텀.

백엔드 · AI 통합

기본 모델은 gemini-3-flash-preview. 스테이지별 모델 설정 SSOT: services/agent-model-settings.service.ts

모델 설정 아키텍처

  • AGENT_MODEL_CATALOG — 코드 기본값 (SSOT)
  • db.agentModelSettings — 어드민 오버라이드 (없으면 기본값)
  • resolveAgentModel(key) — 60초 TTL 캐시 조회, fail-open. 어드민 변경 시 즉시 캐시 무효화
스테이지대표 키모델
검색 전략 수립pre-search-plangemini-3.1-pro-preview
검색어 추출query-extractiongemini-3-flash-preview
관련성 필터relevance-filtergemini-3.5-flash
바이어 탐색 본체supervisorBUYER_SEARCH_V2_MODEL
프레임워크: LangGraph(그래프 기반 에이전트, shared/lead-discovery/) + Mastra(src/mastra/). 멀티 프로바이더(Gemini·Claude·OpenAI)를 Vercel AI SDK로 추상화.

프론트엔드 · 디렉토리 구조

admin/src/ ├── pages/ 라우트 레벨 페이지 (lead-discovery, sequences, email-replies ...) ├── components/ 재사용 UI (ui/ = Radix 프리미티브, app-sidebar/ ...) ├── router/ React Router 7 정의 + lazy-imports.ts (신규 페이지 등록 필수) ├── store/ Jotai atoms (클라 UI 상태) ├── lib/ │ ├── api/ API 레이어 — client.ts · services/*(117) · hooks/*(135) │ ├── auth/ 인증 (storage, session) │ └── permission/ IAM 권한 (PermissionProvider) ├── layouts/ DashboardLayout, RootLayout ├── i18n/ 국제화 (ko/en/id/ja 4개국어) └── main.tsx, App.tsx

프론트엔드 · 상태관리

두 가지 상태를 명확히 구분합니다. 잘못 쓰면 stale 캐시 보안 위험 또는 과도한 동기화 비용이 발생합니다.

TanStack Query 서버 상태

서버가 권위를 가진 데이터. 권한·구독·유저 프로필. queryKey 기반 캐싱, staleTime, 조건부 enabled. 절대 직접 mutate 금지.
useQuery({ queryKey:["leads",p],
  queryFn:()=>leadsApi.search(p),
  staleTime:120_000 })

Jotai 클라 UI 상태

휘발성 UI 선호값. 워크스페이스 선택, 모달 열림, 필터 정렬. atomWithStorage로 localStorage 동기화 가능.
const wsAtom =
  atomWithStorage("selectedWorkspace",null)
const [ws,setWs] = useAtom(wsAtom)

프론트엔드 · API 레이어 (3계층)

[client.ts] httpOnly 쿠키 인증 · CSRF 자동삽입 · 401→refresh→재시도 · Workspace 헤더 ▲ [services/*.ts] (117개) 도메인별 apiFetch 래퍼. leadsApi.search(), sequencesApi.create() ▲ [hooks/*.ts] (135개) useQuery/useMutation 래핑. useLeads(), useCreateSequenceMutation() ▲ [컴포넌트] const { data } = useLeads(params)
백엔드 OpenAPI 스키마 → lib/api/generated/schema.ts 자동 생성(yarn openapi:generate). 신규 API는 service + hook을 분리해서 추가.

프론트엔드 · 인증 · 권한

역할위치
useCurrentUser()현재 유저 (localStorage fast-path + 5분 캐싱)lib/api/hooks/auth.ts
useVerifyToken()쿠키 세션 유효성 확인 (마운트 시 1회)lib/api/hooks/auth.ts
usePermissions()hasPermission(resource,action), isAdminlib/permission/
useBillingSubscription(wsId)구독 tier·trial 상태lib/api/hooks/billing-subscriptions.ts
앱 시작 → AuthProvider → localStorage "user" 로드 (UI fast-path) → POST /auth/verify (쿠키 검증, 실패 시 /signin) → useMyPermissions 자동 활성화 (IAM 권한 매트릭스)

프론트엔드 · 디자인 시스템

SSOT는 admin/.claude/rules/frontend-design.md (희주님 관리 문서, 무단 수정 금지).

  • 색상 토큰: 배경 bg-rd-n-200, 카드 bg-rd-n-white, 제목 text-rd-n-900, 액센트 text-rd-blue-500
  • 타이포: 최소 text-xs(12px) 이상. 한글은 break-keep + [overflow-wrap:anywhere] 조합 필수
  • 아이콘: Material Symbols Rounded → @/components/ui/material-symbol wrapper (lucide 직접 import 금지)
  • i18n: 모든 사용자 노출 텍스트는 t() 키, 4개 언어(ko/en/id/ja) 모두 작성
  • 라우팅: 신규 페이지는 router/lazy-imports.ts에 lazy import 등록 (번들 분할)

주요 도메인 기능

기능설명FE 위치BE 위치
Lead DiscoveryAI 바이어 발굴·검색·분석pages/lead-discovery/services/lead-discovery/ (30+)
Buyer Search에이전트 기반 바이어 탐색pages/lead-discovery/services/buyer-search*/ + Mastra
Sequences이메일 캠페인 생성·실행pages/sequences/services/sequence*.ts
Email Replies받은 답장함·AI 회신·자동분류pages/email-replies/services/email-reply*.ts
LinkedIn SDRLinkedIn 자동화 캠페인pages/linkedin-sdr/services/linkedin-sdr*.ts
Billing구독·결제 (Paddle/Toss)pages/billing/services/billing/ · paddle/

CI / 배포

send-ci.sh — 통합 검증

sh send-ci.sh            # full (기본, quiet) — lint + type-check + build
sh send-ci.sh fast       # pre-commit용 (빠름)
sh send-ci.sh full --verbose   # 전체 스트리밍 (디버깅)
sh send-ci.sh auth --fix # 라우트 권한 맵 + FE IAM 자동보정

환경 & 배포 파이프라인

환경용도URL배포 트리거
local개발localhost:5173 / :3001just dev
alpha스테이징alpha.rinda.aipush origin alpha
beta프로덕션sendgrinda.cloudmerge → beta
로컬 커밋 → push alpha → GHA(ci-cd-alpha.yml): docker build → drizzle-kit migrate → healthcheck → E2E → alpha 라이브 검증 → merge to beta → GHA(ci-cd-beta.yml) 동일 파이프라인 → 프로덕션
CD success ≠ 마이그레이션 적용. healthcheck만 보면 migrate stdout이 안 보입니다. Schema PR 머지 후 직접 확인: ssh alpha "docker logs ... | grep -E 'applying|completed'" (skip 시 다음 호출에서 42703 column does not exist).

E2E 테스트 (헥사고날)

Playwright + 헥사고날 포트/어댑터 패턴. 셀렉터 SSOT는 어댑터 1곳뿐이라 디자인 변경 시 어댑터만 수정하면 모든 spec이 따라옵니다.

테스트 코드 → 포트(인터페이스) → 어댑터(Playwright 구현) app.auth.login() tests/ui/ports.ts adapters/playwright.ts app.nav.go("leads") (셀렉터 비의존) (실제 셀렉터·라이브러리)
cd e2e
npm run bootstrap        # 의존성 + storageState 준비
npm run test:local       # 로컬 (just dev 선행 필요)
npm run test:local:ui    # UI 모드 (브라우저 시각화)
waitForTimeout 금지 (web-first 대기 사용). 기능 변경 시 happy·edge·error·상태분기·게이트 케이스를 빠짐없이 추가. 체크리스트: docs/claude-rules/e2e-harness-workflow.md

시크릿 (Infisical)

환경변수는 코드/.env에 하드코딩하지 않고 Infisical로 관리합니다. 환경: local · alpha · beta.

infisical login
infisical run -e local -- sh send-ci.sh
infisical export -e local -f .env > elysia-server/.env

# Claude Code 스킬 (자연어)
/infisical add GEMINI_API_KEY sk-...
/infisical sync
주요 키: GEMINI/OPENAI/ANTHROPIC_API_KEY, SENDGRID_API_KEY, AWS_SES_*, DB_*, REDIS_*, JWT_SECRET, PADDLE_API_KEY, TOSS_SECRET_KEY.

용어 사전

용어
Lead / Buyer잠재 고객(바이어). 카피상 "Buyer List" 사용 ("Data List" ✕)
Sequence이메일 캠페인. 여러 step으로 구성되어 순차/조건 발송
Enrollment특정 리드가 시퀀스에 편입된 상태 (발송 대상)
Workspace팀/조직 단위. 멤버·권한·구독·데이터의 격리 경계
IAM권한 시스템. resource × action 매트릭스, default-deny (ADR-0001)
Tier구독 등급. trial/pro/team → ai:search 차단, enterprise → full
SSOTSingle Source of Truth — 데이터를 한 곳에만 정의
SSEServer-Sent Events — 서버→클라 단방향 실시간 스트림 (진행률)
Mastra / LangGraphAI 에이전트 워크플로우 프레임워크
drift같은 데이터가 여러 곳에 중복되어 값이 어긋나는 현상
fail-open / fail-closed실패 시 허용(open) / 차단(closed). 라우트 권한은 정적 게이트로 막음
keyset (cursor)OFFSET 대신 마지막 값 기준 페이지네이션 (대용량 성능)

첫 주 체크리스트

Day 1 — 환경 설정

  • git clone + just setup + just dev (admin:5173 / API:3001 확인)
  • admin 대시보드 로그인 / CLAUDE.md · CONTRIBUTING.md 읽기

Day 2–3 — 코드 구조 이해

  • just diagrams로 C4 아키텍처 다이어그램 보기
  • 한 기능을 routes → services → db로 따라 읽기 (예: sequences)
  • FE는 page → hook → service → client 흐름 따라 읽기

Day 4–5 — 개발 & 배포 이해

  • sh send-ci.sh fast 실행 / 작은 버그픽스 또는 기능 추가
  • E2E 테스트 작성 (e2e/tests/features 참고)
  • 로컬 커밋 → pre-commit 훅(마이그레이션 무결성 등) 확인

주간 과제 — 첫 PR

  • 작은 스코프 구현 → sh send-ci.sh full 통과 → E2E happy path 추가 → 리뷰 → 머지
막히면 CLAUDE.md, CONTRIBUTING.md, docs/claude-rules/를 먼저 확인하고, 그래도 안 풀리면 팀 슬랙에 문의하세요.

Rinda 개발자 온보딩 가이드 · 자동 생성 문서 · 2026-06-25