공개 문서

docs/archive/2026-03/newsletter-pipeline/newsletter-pipeline.plan.md

아래는 docs/archive/2026-03/newsletter-pipeline/newsletter-pipeline.plan.md 와 동일한 원문입니다. Markdown과 HTML 변환 결과를 각각 복사할 수 있습니다.

공개 문서 원문 (Markdown)

# [Plan] newsletter-pipeline — Slack + Buttondown 뉴스레터 자동화 파이프라인

**Feature**: newsletter-pipeline
**Date**: 2026-03-01
**Phase**: Plan
**Level**: Dynamic (Next.js + Slack + Buttondown API)
**프로젝트**: slack-automation2

---

## 1. 목적 (Objective)

구독자 등록부터 뉴스레터 발송까지를 **하나의 자동화 파이프라인**으로 연결한다.

- 웹폼 또는 Slack 명령어로 구독자 등록
- 등록 즉시 Slack 알림 자동 전송
- Buttondown API로 구독자 관리 + 뉴스레터 발송
- 모든 활동 기록 저장

---

## 2. 전체 아키텍처 도식

```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  [Pipeline A] 웹폼 경로
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ┌─────────────┐     POST /api/subscribe      ┌─────────────────┐
  │  Web Form   │ ─────────────────────────►   │  Next.js API    │
  │ (이메일 입력) │                              │  Route Handler  │
  └─────────────┘                              └────────┬────────┘
                                                        │
                               ┌────────────────────────┼────────────────────────┐
                               ▼                        ▼                        ▼
                    ┌──────────────────┐   ┌─────────────────────┐   ┌──────────────────┐
                    │  Buttondown API  │   │   Slack Webhook     │   │  DB / 로컬 기록  │
                    │  구독자 등록     │   │   알림 자동 전송     │   │  구독 이력 저장  │
                    │  POST /subscribers│  │  "새 구독자: email" │   │  subscribers.json│
                    └──────────────────┘   └─────────────────────┘   └──────────────────┘


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  [Pipeline B] Slack 슬래시 커맨드 경로
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  Slack 채널
  ┌──────────────────────────┐
  │  /subscribe user@mail.com │ ──► Slack → Next.js /api/slack/subscribe
  └──────────────────────────┘                    │
                                                  ▼
                                       (Pipeline A 와 동일)
                                       Buttondown 등록 + Slack 알림 + 기록


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  [Pipeline C] 뉴스레터 발송 (Buttondown)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  CEO 또는 자동 트리거
  ┌──────────────────┐    POST /api/newsletter/send    ┌──────────────────┐
  │  Slack 명령어    │ ──────────────────────────────► │  Next.js API     │
  │  /newsletter     │    또는 웹 대시보드 버튼          │                  │
  └──────────────────┘                                 └────────┬─────────┘
                                                                │
                                                  ┌─────────────┴──────────────┐
                                                  ▼                            ▼
                                       ┌──────────────────┐      ┌─────────────────────┐
                                       │  Buttondown API  │      │   Slack 알림        │
                                       │  뉴스레터 발송   │      │  "발송 완료: N명"   │
                                       │  POST /emails    │      └─────────────────────┘
                                       └──────────────────┘
```

---

## 3. 핵심 컴포넌트

### 3.1 Next.js API Routes

| 엔드포인트 | 메서드 | 역할 |
|-----------|--------|------|
| `/api/subscribe` | POST | 웹폼 구독자 등록 |
| `/api/slack/subscribe` | POST | Slack 슬래시 커맨드 처리 |
| `/api/slack/events` | POST | Slack 이벤트 수신 |
| `/api/newsletter/send` | POST | 뉴스레터 발송 트리거 |

### 3.2 Buttondown API 연동

| 기능 | API | 설명 |
|------|-----|------|
| 구독자 등록 | `POST /v1/subscribers` | email 등록 |
| 구독자 조회 | `GET /v1/subscribers` | 전체 목록 |
| 뉴스레터 발송 | `POST /v1/emails` | 이메일 발송 |
| 구독 취소 | `DELETE /v1/subscribers/{id}` | 탈퇴 처리 |

### 3.3 Slack 연동

| 기능 | 방식 | 설명 |
|------|------|------|
| `/subscribe <email>` | Slash Command | 구독자 등록 |
| `/newsletter <제목> <내용>` | Slash Command | 뉴스레터 발송 |
| 신규 구독 알림 | Incoming Webhook | 자동 알림 |
| 발송 완료 알림 | Incoming Webhook | 발송 결과 |

### 3.4 기록 저장

| 데이터 | 저장 방식 | 위치 |
|--------|----------|------|
| 구독자 이력 | JSON 파일 | `data/subscribers.json` |
| 발송 이력 | JSON 파일 | `data/newsletters.json` |
| 활동 로그 | 텍스트 | `data/activity.log` |

---

## 4. 기술 스택

| 레이어 | 기술 |
|--------|------|
| Frontend | Next.js 14 (App Router) + Tailwind CSS |
| Backend | Next.js API Routes |
| 뉴스레터 | Buttondown API |
| Slack | Slack Bolt (slash commands) + Incoming Webhook |
| 저장소 | JSON 파일 (MVP) → Supabase (확장 시) |
| 배포 | Vercel |

---

## 5. 환경변수

```env
# Buttondown
BUTTONDOWN_API_KEY=your_api_key

# Slack
SLACK_BOT_TOKEN=xoxb-...
SLACK_SIGNING_SECRET=...
SLACK_WEBHOOK_URL=https://hooks.slack.com/...

# App
NEXT_PUBLIC_APP_URL=https://your-domain.vercel.app
```

---

## 6. 구현 우선순위 (MVP → 확장)

### MVP (즉시 구현)
1. `POST /api/subscribe` — 웹폼 → Buttondown 등록 + Slack 알림
2. 웹폼 UI (이메일 입력 + 제출)
3. Slack Incoming Webhook 알림

### Phase 2 (MVP 검증 후)
4. Slack `/subscribe` 슬래시 커맨드
5. 구독자 기록 저장 (JSON)

### Phase 3 (안정화 후)
6. Slack `/newsletter` 커맨드로 발송 트리거
7. 발송 이력 저장 + 통계

---

## 7. 폴더 구조

```
slack-automation2/
├── src/
│   └── app/
│       ├── page.tsx              # 웹폼 페이지
│       ├── api/
│       │   ├── subscribe/
│       │   │   └── route.ts      # 웹폼 구독 처리
│       │   ├── slack/
│       │   │   ├── subscribe/
│       │   │   │   └── route.ts  # Slack /subscribe 처리
│       │   │   └── events/
│       │   │       └── route.ts  # Slack 이벤트
│       │   └── newsletter/
│       │       └── send/
│       │           └── route.ts  # 뉴스레터 발송
│       └── components/
│           └── SubscribeForm.tsx
├── lib/
│   ├── buttondown.ts             # Buttondown API 클라이언트
│   ├── slack.ts                  # Slack 알림 유틸
│   └── storage.ts                # 기록 저장 유틸
├── data/
│   ├── subscribers.json
│   └── newsletters.json
└── docs/
```

---

## 8. 핵심 플로우 (Buttondown API 코드 미리보기)

```typescript
// lib/buttondown.ts
const BUTTONDOWN_BASE = 'https://api.buttondown.email'

export async function addSubscriber(email: string) {
  const res = await fetch(`${BUTTONDOWN_BASE}/v1/subscribers`, {
    method: 'POST',
    headers: {
      'Authorization': `Token ${process.env.BUTTONDOWN_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ email, type: 'regular' }),
  })
  return res.json()
}

export async function sendNewsletter(subject: string, body: string) {
  const res = await fetch(`${BUTTONDOWN_BASE}/v1/emails`, {
    method: 'POST',
    headers: {
      'Authorization': `Token ${process.env.BUTTONDOWN_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ subject, body, status: 'about_to_send' }),
  })
  return res.json()
}
```

---

## 9. 리스크

| 리스크 | 대응 |
|--------|------|
| Buttondown API 응답 지연 | timeout 처리 + 사용자 피드백 |
| Slack 슬래시 커맨드 3초 응답 제한 | 즉시 ack → 백그라운드 처리 |
| 중복 구독 등록 | Buttondown 중복 거부 처리 |
| API 키 노출 | 환경변수만 사용, 클라이언트 미노출 |

---

## 10. 다음 액션

1. **지금**: `/pdca design newsletter-pipeline` — 상세 설계
2. **설계 후**: `/pdca do newsletter-pipeline` — Next.js 프로젝트 구현 시작

> **PDCA 다음 단계**: `/pdca design newsletter-pipeline`

공개 문서 변환 코드 (HTML)

<h1>[Plan] newsletter-pipeline — Slack + Buttondown 뉴스레터 자동화 파이프라인</h1>
<p><strong>Feature</strong>: newsletter-pipeline
<strong>Date</strong>: 2026-03-01
<strong>Phase</strong>: Plan
<strong>Level</strong>: Dynamic (Next.js + Slack + Buttondown API)
<strong>프로젝트</strong>: slack-automation2</p>
<hr>
<h2>1. 목적 (Objective)</h2>
<p>구독자 등록부터 뉴스레터 발송까지를 <strong>하나의 자동화 파이프라인</strong>으로 연결한다.</p>
<ul>
<li>웹폼 또는 Slack 명령어로 구독자 등록</li>
<li>등록 즉시 Slack 알림 자동 전송</li>
<li>Buttondown API로 구독자 관리 + 뉴스레터 발송</li>
<li>모든 활동 기록 저장</li>
</ul>
<hr>
<h2>2. 전체 아키텍처 도식</h2>
<pre><code>━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  [Pipeline A] 웹폼 경로
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ┌─────────────┐     POST /api/subscribe      ┌─────────────────┐
  │  Web Form   │ ─────────────────────────►   │  Next.js API    │
  │ (이메일 입력) │                              │  Route Handler  │
  └─────────────┘                              └────────┬────────┘
                                                        │
                               ┌────────────────────────┼────────────────────────┐
                               ▼                        ▼                        ▼
                    ┌──────────────────┐   ┌─────────────────────┐   ┌──────────────────┐
                    │  Buttondown API  │   │   Slack Webhook     │   │  DB / 로컬 기록  │
                    │  구독자 등록     │   │   알림 자동 전송     │   │  구독 이력 저장  │
                    │  POST /subscribers│  │  &quot;새 구독자: email&quot; │   │  subscribers.json│
                    └──────────────────┘   └─────────────────────┘   └──────────────────┘


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  [Pipeline B] Slack 슬래시 커맨드 경로
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  Slack 채널
  ┌──────────────────────────┐
  │  /subscribe user@mail.com │ ──► Slack → Next.js /api/slack/subscribe
  └──────────────────────────┘                    │
                                                  ▼
                                       (Pipeline A 와 동일)
                                       Buttondown 등록 + Slack 알림 + 기록


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  [Pipeline C] 뉴스레터 발송 (Buttondown)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  CEO 또는 자동 트리거
  ┌──────────────────┐    POST /api/newsletter/send    ┌──────────────────┐
  │  Slack 명령어    │ ──────────────────────────────► │  Next.js API     │
  │  /newsletter     │    또는 웹 대시보드 버튼          │                  │
  └──────────────────┘                                 └────────┬─────────┘
                                                                │
                                                  ┌─────────────┴──────────────┐
                                                  ▼                            ▼
                                       ┌──────────────────┐      ┌─────────────────────┐
                                       │  Buttondown API  │      │   Slack 알림        │
                                       │  뉴스레터 발송   │      │  &quot;발송 완료: N명&quot;   │
                                       │  POST /emails    │      └─────────────────────┘
                                       └──────────────────┘
</code></pre>
<hr>
<h2>3. 핵심 컴포넌트</h2>
<h3>3.1 Next.js API Routes</h3>
<table>
<thead>
<tr>
<th>엔드포인트</th>
<th>메서드</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td><code>/api/subscribe</code></td>
<td>POST</td>
<td>웹폼 구독자 등록</td>
</tr>
<tr>
<td><code>/api/slack/subscribe</code></td>
<td>POST</td>
<td>Slack 슬래시 커맨드 처리</td>
</tr>
<tr>
<td><code>/api/slack/events</code></td>
<td>POST</td>
<td>Slack 이벤트 수신</td>
</tr>
<tr>
<td><code>/api/newsletter/send</code></td>
<td>POST</td>
<td>뉴스레터 발송 트리거</td>
</tr>
</tbody></table>
<h3>3.2 Buttondown API 연동</h3>
<table>
<thead>
<tr>
<th>기능</th>
<th>API</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>구독자 등록</td>
<td><code>POST /v1/subscribers</code></td>
<td>email 등록</td>
</tr>
<tr>
<td>구독자 조회</td>
<td><code>GET /v1/subscribers</code></td>
<td>전체 목록</td>
</tr>
<tr>
<td>뉴스레터 발송</td>
<td><code>POST /v1/emails</code></td>
<td>이메일 발송</td>
</tr>
<tr>
<td>구독 취소</td>
<td><code>DELETE /v1/subscribers/{id}</code></td>
<td>탈퇴 처리</td>
</tr>
</tbody></table>
<h3>3.3 Slack 연동</h3>
<table>
<thead>
<tr>
<th>기능</th>
<th>방식</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>/subscribe &lt;email&gt;</code></td>
<td>Slash Command</td>
<td>구독자 등록</td>
</tr>
<tr>
<td><code>/newsletter &lt;제목&gt; &lt;내용&gt;</code></td>
<td>Slash Command</td>
<td>뉴스레터 발송</td>
</tr>
<tr>
<td>신규 구독 알림</td>
<td>Incoming Webhook</td>
<td>자동 알림</td>
</tr>
<tr>
<td>발송 완료 알림</td>
<td>Incoming Webhook</td>
<td>발송 결과</td>
</tr>
</tbody></table>
<h3>3.4 기록 저장</h3>
<table>
<thead>
<tr>
<th>데이터</th>
<th>저장 방식</th>
<th>위치</th>
</tr>
</thead>
<tbody><tr>
<td>구독자 이력</td>
<td>JSON 파일</td>
<td><code>data/subscribers.json</code></td>
</tr>
<tr>
<td>발송 이력</td>
<td>JSON 파일</td>
<td><code>data/newsletters.json</code></td>
</tr>
<tr>
<td>활동 로그</td>
<td>텍스트</td>
<td><code>data/activity.log</code></td>
</tr>
</tbody></table>
<hr>
<h2>4. 기술 스택</h2>
<table>
<thead>
<tr>
<th>레이어</th>
<th>기술</th>
</tr>
</thead>
<tbody><tr>
<td>Frontend</td>
<td>Next.js 14 (App Router) + Tailwind CSS</td>
</tr>
<tr>
<td>Backend</td>
<td>Next.js API Routes</td>
</tr>
<tr>
<td>뉴스레터</td>
<td>Buttondown API</td>
</tr>
<tr>
<td>Slack</td>
<td>Slack Bolt (slash commands) + Incoming Webhook</td>
</tr>
<tr>
<td>저장소</td>
<td>JSON 파일 (MVP) → Supabase (확장 시)</td>
</tr>
<tr>
<td>배포</td>
<td>Vercel</td>
</tr>
</tbody></table>
<hr>
<h2>5. 환경변수</h2>
<pre><code class="language-env"># Buttondown
BUTTONDOWN_API_KEY=your_api_key

# Slack
SLACK_BOT_TOKEN=xoxb-...
SLACK_SIGNING_SECRET=...
SLACK_WEBHOOK_URL=https://hooks.slack.com/...

# App
NEXT_PUBLIC_APP_URL=https://your-domain.vercel.app
</code></pre>
<hr>
<h2>6. 구현 우선순위 (MVP → 확장)</h2>
<h3>MVP (즉시 구현)</h3>
<ol>
<li><code>POST /api/subscribe</code> — 웹폼 → Buttondown 등록 + Slack 알림</li>
<li>웹폼 UI (이메일 입력 + 제출)</li>
<li>Slack Incoming Webhook 알림</li>
</ol>
<h3>Phase 2 (MVP 검증 후)</h3>
<ol start="4">
<li>Slack <code>/subscribe</code> 슬래시 커맨드</li>
<li>구독자 기록 저장 (JSON)</li>
</ol>
<h3>Phase 3 (안정화 후)</h3>
<ol start="6">
<li>Slack <code>/newsletter</code> 커맨드로 발송 트리거</li>
<li>발송 이력 저장 + 통계</li>
</ol>
<hr>
<h2>7. 폴더 구조</h2>
<pre><code>slack-automation2/
├── src/
│   └── app/
│       ├── page.tsx              # 웹폼 페이지
│       ├── api/
│       │   ├── subscribe/
│       │   │   └── route.ts      # 웹폼 구독 처리
│       │   ├── slack/
│       │   │   ├── subscribe/
│       │   │   │   └── route.ts  # Slack /subscribe 처리
│       │   │   └── events/
│       │   │       └── route.ts  # Slack 이벤트
│       │   └── newsletter/
│       │       └── send/
│       │           └── route.ts  # 뉴스레터 발송
│       └── components/
│           └── SubscribeForm.tsx
├── lib/
│   ├── buttondown.ts             # Buttondown API 클라이언트
│   ├── slack.ts                  # Slack 알림 유틸
│   └── storage.ts                # 기록 저장 유틸
├── data/
│   ├── subscribers.json
│   └── newsletters.json
└── docs/
</code></pre>
<hr>
<h2>8. 핵심 플로우 (Buttondown API 코드 미리보기)</h2>
<pre><code class="language-typescript">// lib/buttondown.ts
const BUTTONDOWN_BASE = &#39;https://api.buttondown.email&#39;

export async function addSubscriber(email: string) {
  const res = await fetch(`${BUTTONDOWN_BASE}/v1/subscribers`, {
    method: &#39;POST&#39;,
    headers: {
      &#39;Authorization&#39;: `Token ${process.env.BUTTONDOWN_API_KEY}`,
      &#39;Content-Type&#39;: &#39;application/json&#39;,
    },
    body: JSON.stringify({ email, type: &#39;regular&#39; }),
  })
  return res.json()
}

export async function sendNewsletter(subject: string, body: string) {
  const res = await fetch(`${BUTTONDOWN_BASE}/v1/emails`, {
    method: &#39;POST&#39;,
    headers: {
      &#39;Authorization&#39;: `Token ${process.env.BUTTONDOWN_API_KEY}`,
      &#39;Content-Type&#39;: &#39;application/json&#39;,
    },
    body: JSON.stringify({ subject, body, status: &#39;about_to_send&#39; }),
  })
  return res.json()
}
</code></pre>
<hr>
<h2>9. 리스크</h2>
<table>
<thead>
<tr>
<th>리스크</th>
<th>대응</th>
</tr>
</thead>
<tbody><tr>
<td>Buttondown API 응답 지연</td>
<td>timeout 처리 + 사용자 피드백</td>
</tr>
<tr>
<td>Slack 슬래시 커맨드 3초 응답 제한</td>
<td>즉시 ack → 백그라운드 처리</td>
</tr>
<tr>
<td>중복 구독 등록</td>
<td>Buttondown 중복 거부 처리</td>
</tr>
<tr>
<td>API 키 노출</td>
<td>환경변수만 사용, 클라이언트 미노출</td>
</tr>
</tbody></table>
<hr>
<h2>10. 다음 액션</h2>
<ol>
<li><strong>지금</strong>: <code>/pdca design newsletter-pipeline</code> — 상세 설계</li>
<li><strong>설계 후</strong>: <code>/pdca do newsletter-pipeline</code> — Next.js 프로젝트 구현 시작</li>
</ol>
<blockquote>
<p><strong>PDCA 다음 단계</strong>: <code>/pdca design newsletter-pipeline</code></p>
</blockquote>