AWS SES와 SNS를 활용한 고성능 대량 이메일 발송 및 모니터링 서버입니다. Rust와 Tokio를 기반으로 구축되어 높은 처리량과 안정성을 제공합니다.
| 기능 | 설명 |
|---|---|
| 대량 발송 | 요청당 최대 10,000건 |
| 예약 발송 | scheduled_at 파라미터로 지정 |
| 실시간 모니터링 | AWS SNS를 통한 발송 결과 수신 |
| 오픈 트래킹 | 1x1 투명 픽셀로 열람 추적 |
| 발송 취소 | 토픽별 대기 중인 이메일 취소 |
| 토픽 통계 | 상태별 발송 현황 조회 |
| 구성 요소 | 기술 |
|---|---|
| Backend | Rust + Axum |
| Email Service | AWS SES v2 |
| Notification | AWS SNS |
| Async Runtime | Tokio |
| Database | SQLite (WAL mode) |
| 인증 | X-API-KEY 헤더 |
| 모니터링 | Sentry + tracing |
flowchart TB
subgraph Client["클라이언트"]
API[API 요청]
end
subgraph Server["이메일 발송 서버"]
Handler[Message Handler]
Scheduler[Scheduler]
Sender[Sender Service]
Receiver[Receiver Service]
end
subgraph Database["데이터베이스"]
SQLite[(SQLite WAL)]
end
subgraph AWS["AWS Cloud"]
SES[AWS SES v2]
SNS[AWS SNS]
end
API -->|POST /v1/messages| Handler
Handler -->|배치 INSERT| SQLite
Handler -->|즉시 발송| Sender
Scheduler -->|10초마다 폴링| SQLite
Scheduler -->|예약 메일 픽업| Sender
Sender -->|Rate Limited| SES
SES -->|발송 이벤트| SNS
SNS -->|Webhook| Receiver
Receiver -->|상태 업데이트| SQLite
flowchart LR
subgraph Input["요청 수신"]
A[API 요청] --> B{scheduled_at?}
end
subgraph Immediate["즉시 발송"]
B -->|No| C[DB 저장]
C --> D[Channel 전송]
D --> E[Rate Limiter]
end
subgraph Scheduled["예약 발송"]
B -->|Yes| F[Created 상태 저장]
F --> G[Scheduler 폴링]
G -->|10초마다| H[UPDATE...RETURNING]
H --> E
end
subgraph Sending["발송 처리"]
E --> I[Token 획득]
I --> J[AWS SES 호출]
J --> K[결과 업데이트]
end
subgraph Result["결과 수신"]
L[AWS SNS] -->|Webhook| M[Receiver]
M --> N[배치 UPDATE]
end
J -.-> L
클라이언트가 scheduled_at 없이 요청하면 즉시 발송됩니다.
sequenceDiagram
autonumber
participant C as Client
participant H as Handler
participant DB as SQLite
participant S as Sender
participant SES as AWS SES
C->>H: POST /v1/messages
H->>DB: Content 저장 (중복 방지)
H->>DB: 배치 INSERT (150건씩)
H->>S: Channel로 전송
H-->>C: 응답 반환
loop Rate Limited (Token Bucket)
S->>S: 토큰 획득 대기
S->>SES: 이메일 발송
SES-->>S: Message ID 반환
S->>DB: 배치 UPDATE (100건씩)
end
scheduled_at이 포함된 요청은 지정 시간까지 대기 후 발송됩니다.
sequenceDiagram
autonumber
participant C as Client
participant H as Handler
participant DB as SQLite
participant Sch as Scheduler
participant S as Sender
participant SES as AWS SES
C->>H: POST /v1/messages<br/>(scheduled_at 포함)
H->>DB: Content 저장
H->>DB: Created 상태로 저장
H-->>C: 응답 반환 (scheduled: true)
loop 10초마다 폴링
Sch->>DB: UPDATE...RETURNING<br/>(원자적 픽업)
DB-->>Sch: 발송 대상 메일
Sch->>S: Channel로 전송
end
loop Rate Limited
S->>SES: 이메일 발송
SES-->>S: Message ID 반환
S->>DB: 상태 업데이트
end
AWS SES에서 발송 결과를 SNS를 통해 수신합니다.
flowchart LR
subgraph AWS["AWS Cloud"]
SES[AWS SES]
SNS[AWS SNS]
end
subgraph Events["이벤트 타입"]
D[Delivery<br/>발송 성공]
B[Bounce<br/>반송]
C[Complaint<br/>스팸 신고]
end
subgraph Server["이메일 서버"]
R[Receiver Service]
DB[(SQLite)]
end
SES -->|발송 이벤트| SNS
SNS --> D & B & C
D & B & C -->|POST /v1/events/results| R
R -->|배치 UPDATE| DB
Token Bucket과 Semaphore를 조합한 이벤트 드리븐 방식입니다.
flowchart TB
subgraph TokenBucket["Token Bucket"]
T[토큰 풀]
R[100ms마다<br/>10% 리필]
end
subgraph Semaphore["Semaphore"]
S[동시 요청 제한]
N["Rate Limit × 2"]
end
subgraph Sender["발송 프로세스"]
A[1. 토큰 획득]
B[2. Semaphore 획득]
C[3. SES API 호출]
D[4. 리소스 해제]
end
R -.->|Notify| T
T --> A
A --> B
S --> B
B --> C
C --> D
D -.->|토큰 반환| T
D -.->|Permit 반환| S
erDiagram
EMAIL_CONTENT {
string id PK
string subject
string content
datetime created_at
}
EMAIL_REQUEST {
string id PK
string topic_id
string email
string content_id FK
string status
string message_id
datetime scheduled_at
datetime created_at
}
EMAIL_RESULT {
string id PK
string request_id FK
string result_type
string bounce_type
datetime created_at
}
EMAIL_CONTENT ||--o{ EMAIL_REQUEST : "has"
EMAIL_REQUEST ||--o| EMAIL_RESULT : "has"
| 구성 요소 | 방식 | 특징 |
|---|---|---|
| Token Bucket | Notify 기반 |
폴링 없는 이벤트 드리븐 |
| Semaphore | 동시 요청 제한 | Rate Limit의 2배 |
| 리필 | 100ms마다 10% | 균등 분배 |
| 최적화 | 효과 |
|---|---|
| WAL 모드 | 쓰기 중 동시 읽기 가능 |
| mmap 256MB | 메모리 맵 I/O |
| 캐시 64MB | 인메모리 캐시 |
| 배치 INSERT | 10배 이상 성능 향상 |
| CASE WHEN UPDATE | 벌크 업데이트 |
| UPDATE...RETURNING | 원자적 스케줄러 픽업 |
| 복합 인덱스 | 쿼리 최적화 |
| 기법 | 효과 |
|---|---|
Arc<String> |
Subject/content 공유 (10,000건 발송 시 1회 할당) |
Vec::with_capacity() |
재할당 방지 |
| 지연 복사 | 트래킹 픽셀을 발송 시점에 추가 |
| 리소스 | 설정 |
|---|---|
| SES 클라이언트 | OnceCell 싱글톤 |
| DB 풀 | 5-20개 연결 |
| 발송 채널 | 10,000개 버퍼 |
| 후처리 채널 | 1,000개 버퍼 |
-
샌드박스 해제 (프로덕션)
-
도메인 인증
- AWS SES 콘솔에서 도메인 등록
- DNS에 DKIM, SPF 레코드 추가
-
이메일 인증 (샌드박스)
- 발신자 이메일 등록
- SNS 주제 생성
- SES 이벤트 대상 추가 (Bounce, Complaint, Delivery)
- HTTP 구독 설정 (
/v1/events/results)
| 변수 | 필수 | 기본값 | 설명 |
|---|---|---|---|
SERVER_PORT |
8080 | 서버 포트 | |
SERVER_URL |
O | 외부 접근 URL | |
API_KEY |
O | API 인증 키 | |
AWS_REGION |
ap-northeast-2 | AWS 리전 | |
AWS_ACCESS_KEY_ID |
O | AWS 액세스 키 | |
AWS_SECRET_ACCESS_KEY |
O | AWS 시크릿 키 | |
AWS_SES_FROM_EMAIL |
O | 발신자 이메일 | |
MAX_SEND_PER_SECOND |
24 | 초당 최대 발송량 | |
SENTRY_DSN |
Sentry DSN | ||
RUST_LOG |
info | 로그 레벨 |
# 서버 실행 (마이그레이션 자동 적용)
cargo run --release
# Docker
docker build -t ses-sender .
docker run -p 3000:3000 --env-file .env ses-sender데이터베이스 마이그레이션은 서버 시작 시 자동으로 적용됩니다 (
migrations/폴더)
POST /v1/messages
X-API-KEY: {api_key}
Content-Type: application/json{
"messages": [
{
"topic_id": "newsletter_2024_01",
"emails": ["user1@example.com", "user2@example.com"],
"subject": "1월 뉴스레터",
"content": "<h1>안녕하세요!</h1><p>...</p>"
}
],
"scheduled_at": "2024-01-01 09:00:00"
}응답:
{
"total": 2,
"success": 2,
"errors": 0,
"duration_ms": 45,
"scheduled": true
}| 엔드포인트 | 메서드 | 설명 |
|---|---|---|
/v1/events/open?request_id={id} |
GET | 오픈 트래킹 (1x1 PNG) |
/v1/events/counts/sent?hours=24 |
GET | 발송 건수 조회 |
/v1/events/results |
POST | SNS 이벤트 수신 |
| 엔드포인트 | 메서드 | 설명 |
|---|---|---|
/v1/topics/{topic_id} |
GET | 통계 조회 |
/v1/topics/{topic_id} |
DELETE | 발송 취소 |
| 엔드포인트 | 설명 | 인증 |
|---|---|---|
/health |
기본 헬스 체크 | |
/ready |
DB 연결 확인 |
src/
├── main.rs # 진입점, Graceful Shutdown
├── app.rs # 라우터 설정
├── config.rs # 환경변수
├── constants.rs # 상수 (BATCH_INSERT_SIZE)
├── state.rs # 애플리케이션 상태
├── handlers/
│ ├── message_handlers.rs # 이메일 발송 API
│ ├── event_handlers.rs # SNS 이벤트, 오픈 트래킹
│ ├── health_handlers.rs # 헬스 체크
│ └── topic_handlers.rs # 토픽 관리
├── services/
│ ├── scheduler.rs # 예약 이메일 조회
│ ├── receiver.rs # Rate-limited 발송, 배치 업데이트
│ └── sender.rs # AWS SES API 호출
├── models/
│ ├── content.rs # EmailContent
│ ├── request.rs # EmailRequest (Arc<String>)
│ └── result.rs # EmailResult
├── middlewares/
│ └── auth_middlewares.rs # API Key 인증
└── tests/ # 테스트
cargo fmt # 포맷팅
cargo clippy # 린터cargo build # 개발
cargo build --release # 릴리즈
cargo check # 검사만cargo test # 전체 테스트
cargo test -- --nocapture # 출력 포함
cargo test test_save_batch # 특정 테스트RUST_LOG=debug cargo run # 상세 로그
RUST_LOG=info cargo run # 일반 운영
RUST_LOG=warn cargo run # 경고만| 크레이트 | 용도 |
|---|---|
| axum | 웹 프레임워크 |
| tokio | 비동기 런타임 |
| sqlx | SQLite |
| aws-sdk-sesv2 | AWS SES |
| serde | 직렬화 |
| tracing | 로깅 |
| sentry | 에러 트래킹 |
MIT License