# Mail Studio

メール配信のオープンソース CRM。**エキスパ / UTAGE の代替**を、あなた自身の Cloudflare アカウントで動かします。
サーバー代 **0円**（Cloudflare 無料枠 + Resend 無料枠）。Claude Code から全操作・全改造が可能。

**現バージョン**: v1.1.1 ・ MIT License ・ TypeScript / Cloudflare Workers + D1 + Resend

---

## なぜ Mail Studio？

| | エキ◯パ | UT◯GE | **Mail Studio** |
|---|---|---|---|
| 月額 | 3,800円〜 | 16,500円〜 | **0円** |
| 一斉配信 | ✅ | ✅ | ✅ |
| ステップメール | ✅ | ✅ | ✅ |
| 登録フォーム（埋め込み可） | ✅ | ✅ | ✅ |
| 開封・クリック計測 | ✅ | ✅ | ✅ |
| 配信停止・バウンス自動処理 | ✅ | ✅ | ✅ |
| **ポイント & 抽選** | ❌ | ❌ | **✅** |
| **読者マイページ** | ❌ | 一部 | **✅** |
| 外部 Webhook 連携 | 一部 | 一部 | **✅ 双方向** |
| LINE / X 連携 | ❌ | 一部 | **✅** |
| Claude Code (AI) 対応 | ❌ | ❌ | **指示書 同梱** |
| 配信数の従量課金 | あり | あり | **なし**（Resend の枠内） |
| ソースコード | 非公開 | 非公開 | **MIT（全部あなたのもの）** |
| セルフホスト | ❌ | ❌ | **✅** |

> 月額課金のツールと違い、**登録者が増えても料金は上がりません**。Resend の無料枠は月3,000通・1日100通。有料でも月$20で5万通です。

---

## クイックスタート

### 1コマンドでセットアップ

```bash
npm install
bash scripts/setup.sh
```

ウィザードが以下を全部やります:

- Cloudflare アカウント認証（`wrangler login`）
- D1 データベース作成 + スキーマ適用
- セッション鍵の自動生成、管理パスワード・Resend キーの安全な登録
- 送信元アドレス・送信者表示の設定
- Worker のデプロイ

所要時間 約5分。完了すると管理画面の URL が表示されます。

> **Windows の方**: Git Bash か WSL で実行してください。それも難しければ後述の Claude Code に任せるのが確実です。

### 必要なもの

- Node.js 18+
- Cloudflare アカウント（無料枠でOK）
- Resend アカウント（無料枠でOK）+ 送信用の独自ドメイン
- ※ ドメイン代（年1,000〜2,000円程度）以外に固定費はかかりません

---

## Claude Code に丸ごと任せる（推奨）

このフォルダには **Claude Code 用の指示書 `CLAUDE.md` が同梱**されています。
フォルダ内で `claude` を起動して、こう話しかけるだけです。

```
セットアップガイド.md に従って、セットアップを最初から手伝ってください
```

Claude があなたの環境を診断しながら、Cloudflare の登録から Resend のドメイン認証、デプロイ、初回テスト送信まで1ステップずつ案内・代行します。
セットアップ後も、**エラー解決・使い方の質問・機能の追加改造**をそのまま頼めます。

```
登録フォームに「会社名」の入力欄を追加してください
ステップメールを5通のシナリオにしたいので設計から手伝ってください
```

---

## 主要機能

### 配信

- **一斉配信** — 下書き → 予約 → **承認** → 自動送信。承認ゲートで誤送信を防止
- **ステップメール** — 登録をトリガーに自動開始。即時 / N日後 / 時刻指定で1通ずつ配信
- **予約配信** — Cron（毎分）で指定時刻に自動実行。手動での「今すぐ実行」も可能
- **差し込み** — 購読者名などを本文に埋め込み

### 集客

- **登録フォーム** — 連携リンクを発行すると、そのまま使える登録ページが手に入る。`<iframe>` で自分のサイトに埋め込み可能
- **汎用 Webhook** — 自作 LP・決済・外部フォームから `POST` するだけで購読者登録（CORS 対応）
- **リスト管理** — 複数リスト、購読者のリスト間移動、CSV 取込
- **登録 → 配信の自動接続** — 連携リンクにステップ施策を紐付ければ、登録した瞬間に配信が始まる

### エンゲージメント

- **開封・クリック計測** — 1x1 GIF とリダイレクトで自動計測
- **ポイント** — 開封・クリック・登録などの行動に応じて自動付与（台帳方式・冪等）
- **抽選くじ** — 確率と当選本数の上限を設定して読者が挑戦できる
- **ポイント交換** — 在庫管理つきの景品交換。申込・発送ステータス管理、キャンセル時はポイント自動返却
- **読者マイページ** — 購読者ごとの固定 URL で残高確認・抽選・交換ができる

### 到達率・コンプライアンス

- **配信停止リンクを全メールに自動付与**（1クリックで即時停止）
- **送信者表示を自動付与** — 特定電子メール法の必須表示
- **バウンス / 苦情の自動反映** — Resend Webhook を受けて購読者状態を更新、以後送信対象外に
- **除外の自動化** — 配信停止・バウンス済みには構造的に送信されない

### 外部連携

- **LINE Harness 連携** — LINE 側の登録・ゲート通過をトリガーにメール配信を開始
- **X Harness 連携** — X 側のエンゲージメントゲート通過をトリガーにメール配信を開始
- **双方向 Webhook** — 任意の外部ツールと接続可能

---

## 安全設計

**初期状態では、どんな操作をしても実メールは1通も送信されません。**

| 安全装置 | 既定 | 内容 |
|---|---|---|
| `DRY_RUN` | `"true"` | 実送信を停止し、ログのみ記録 |
| 承認ゲート | 未承認 | 「承認済み」にするまで配信されない |
| 二重送信防止 | 常時 | DB 一意制約 + 予約の楽観ロック + Resend `Idempotency-Key` |
| `MAX_SENDS_PER_TICK` | `100` | 1処理あたりの送信上限 |
| `MAX_SENDS_PER_DAY` | `2000` | 1日の送信上限 |
| 除外リスト | 常時 | 配信停止・バウンス済みは送信対象から除外 |

実送信への切り替えは、**自分だけのリストで1通テストして中身を確認したあと**に行う設計です。詳しくは `SECURITY.md`。

---

## 技術スタック

| レイヤー | 技術 |
|---|---|
| API / 公開エンドポイント | Cloudflare Workers + Hono |
| データベース | Cloudflare D1 (SQLite) |
| 定期実行 | Workers Cron Triggers（毎分） |
| メール送信 | Resend API |
| 管理画面 | 素の SPA（ビルド不要・`public/`） |
| 言語 | TypeScript (strict) |
| テスト | Vitest（99件・Cloudflare 不要） |

## アーキテクチャ

```
外部LP / SNS / フォーム
   │  POST /api/hooks/:token   ← 汎用Webhook
   │  GET|POST /f/:token       ← 埋め込み登録フォーム
   ▼
Cloudflare Workers (Hono) ──── D1 (SQLite)
   │                             ▲
   │  Cron (*/1 * * * *)         │
   │    ├ 予約配信の送信          │
   │    └ ステップメールの進行 ───┘
   ▼
Resend API ──→ 受信者
   │
   └ Webhook (bounce/complaint) ──→ POST /api/resend/webhook

購読者 ──→ GET /t/o/... (開封計測)  ──→ ポイント付与
      ──→ GET /t/c/... (クリック計測) ──→ ポイント付与
      ──→ GET /p/:token (マイページ)  ──→ 抽選・交換
```

中核ロジック（`src/core.ts` / `src/points.ts`）は Store / Mailer 抽象に対する純粋関数で、**Cloudflare アカウントなしでテストできます**。

---

## ディレクトリ

```
mail-studio/
├── src/                  Worker本体（app / core / points / store / d1store / mailer …）
├── public/               管理画面SPA + 使い方マニュアル
├── tests/                単体 / 結合 / E2E テスト（99件）
├── docs/                 SRS(要件) / SDD(設計) / TEST_PLAN / E2Eシナリオ
├── scripts/setup.sh      対話式セットアップウィザード
├── schema_full.sql       DB初期化（1本で完結・再実行可）
├── wrangler.jsonc        Cloudflare設定（ここを書き換えて使う）
├── CLAUDE.md             Claude Code 用の指示書
├── セットアップガイド.md   全手順（人間向け完全版）
├── SECURITY.md           鍵の扱い・法令・誤送信防止
└── SUPPORT.md            困ったときの対処
```

---

## 手動セットアップ（ウィザードを使わない場合）

```bash
npm install

# 1. D1作成 → 返ってきた database_id を wrangler.jsonc に反映
npx wrangler d1 create mail_db

# 2. スキーマ適用
npm run db:init:remote

# 3. シークレット登録（値は画面に表示されません）
npx wrangler secret put ADMIN_PASSWORD
npx wrangler secret put SESSION_SECRET
npx wrangler secret put RESEND_API_KEY

# 4. wrangler.jsonc の vars を編集
#    MAIL_FROM   = Resendで認証済みドメインのアドレス
#    SENDER_INFO = 送信者の氏名/名称・住所・連絡先（法令上必須）

# 5. デプロイ（この時点でも DRY_RUN=true ＝ 送信されません）
npm run deploy

# 6. 自分だけのリストで1通テスト → 問題なければ DRY_RUN を "false" にして再デプロイ
```

## 開発

```bash
npm test          # 99件のテスト（Cloudflare不要）
npm run typecheck # 型チェック
npm run db:init   # ローカルD1にスキーマ適用
npm run dev       # http://localhost:8787
npm run deploy    # 本番デプロイ
```

改造したら `npm test` と `npm run typecheck` を通してからデプロイしてください。

---

## ライセンス

MIT License — 商用利用・改変・再配布すべて自由です。詳細は `LICENSE`。
