TiedWorkspace ご招待フォーム (invite-form)
利用規約を掲示して同意を取得し、設定済みの招待リンクをメールで配信するアプリ。 承認済みの候補者へワークスペース参加用リンクを届ける、招待フロー の 最終ステップを担う内製フォーム。
- アプリ:
apps/invite-app - Worker 名:
invite-web(React Router v7 framework モード / SSR on Cloudflare Workers) - 公開 URL (予定):
https://invite.tied-workspace.com
管理 UI は別アプリ
招待リンク・メール文面の設定や、同意・送信履歴の閲覧といった管理 UI は本アプリには含めず、 別アプリ tied-workspace-admin が担う。本アプリは認証を 持たない公開フォームに徹し、D1 の invite_settings は読み取りのみ行う。
全体像
本アプリは未認証で開ける公開フォーム (/) のみを持つ。
flowchart TD
A["候補者: /(フォーム)"] --> B["利用規約を掲示"]
B --> C{"同意して送信"}
C -->|入力不備| B
C -->|OK| T{"Turnstile 検証"}
T -->|失敗| B
T -->|OK| D["D1 invitations に同意を記録 (pending)"]
D --> E["invite_settings の招待リンクを取得 (読み取り)"]
E --> F["Cloudflare send_email で配信"]
F -->|成功| G["invitations を sent に更新 → 完了画面"]
F -->|失敗| H["invitations を failed に更新 → 再送案内"]
I["tied-workspace-admin (別アプリ)"] -.->|招待リンク/文面を設定 (書き込み)| E
I -.->|履歴閲覧| D設計の不変条件
- 公開フォームのみ・認証なし: 本アプリは候補者が未認証で開ける公開フォームに徹する。 管理 UI と認証 (better-auth) は持たず、別アプリ
tied-workspace-adminに分離する。 したがって Cloudflare Access も使わない。 invite_settingsは読み取り専用: 招待リンク・メール文面の設定 (書き込み) はtied-workspace-adminが行う。本アプリは設定を読むだけで、未設定ならフォームを受付停止にする。- 同意は記録してから配信する: 送信は
invitationsにpendingで作成し、メール送信の成否でsent/failedに更新する。失敗分も記録に残り、admin アプリ側で把握・再送できる。 - 利用規約はコードが正本: 規約本文は
apps/invite-app/app/lib/terms.tsに構造化して持つ。 改定したらTERMS_VERSIONを更新する。同意記録 (invitations.terms_version) は同意時点の バージョンを保存し、どの版に同意したかを後から追跡できる。 - メール送信は差し替え可能: 配信は
EmailSenderポート (app/lib/email.server.ts) に 抽象化する。既定は Cloudflare Email Routing のsend_emailバインディング (cloudflare:email)。別プロバイダ (Resend 等) へはポートを差し替えるだけで移行できる。 - 公開フォームはボット対策必須: 未認証でメール配信を起動できるため、送信時に Cloudflare Turnstile のトークンをサーバー側 (
app/lib/turnstile.server.ts) で検証し、 ボット/スパムを弾く。検証に失敗したら同意記録もメール配信もしない。
アーキテクチャ
ロジックは app/lib/ にサーバー専用モジュールとして閉じ、ルート (loader / action) は app/lib/deps.server.ts 経由でのみそれらを呼ぶ。
apps/invite-app/
├── app/
│ ├── root.tsx # 共通レイアウト (公開・認証なし)
│ ├── routes.ts
│ ├── components/terms-view.tsx # 利用規約の読み取り専用ビュー
│ ├── routes/index.tsx # 公開フォーム (loader: 受付可否 / action: 同意送信)
│ └── lib/
│ ├── deps.server.ts # ルートが使う依存の集約 / 組み立て
│ ├── terms.ts # 利用規約 (TERMS_VERSION / 条文)
│ ├── invitations.server.ts # D1 データ層 (invite_settings 読取 / invitations 書込)
│ ├── email.server.ts # EmailSender ポート + Cloudflare 実装
│ ├── turnstile.server.ts # Cloudflare Turnstile トークン検証 (ボット対策)
│ └── invite-flow.server.ts # 同意送信ユースケース (submitConsent)
└── workers/app.ts # Cloudflare Worker エントリ (fetch)データモデル
D1 tied-workspace に 2 テーブル (スキーマは packages/d1/migrations/tied-workspace/0002_invite_form.sql)。 本アプリは invite_settings を読み取り、invitations に書き込む。invite_settings の設定は tied-workspace-admin が行う。
invite_settings (招待設定 / 単一行 id='default')
| カラム | 説明 |
|---|---|
invite_url | 配信する招待リンク (例: Slack ワークスペース参加 URL) |
email_subject | メール件名 |
email_body | 本文テンプレート。 / を差し込む |
updated_at / updated_by | 最終更新時刻 / 更新者 (admin アプリが記録) |
invitations (同意・送信記録 / 1 行 = 1 通)
| カラム | 説明 |
|---|---|
name / email | フォーム入力 |
terms_version | 同意した利用規約のバージョン |
status | pending / sent / failed |
email_error | 送信失敗時のエラー要約 |
ip_address / user_agent | 同意取得時のメタ情報 |
created_at / sent_at | 受付時刻 / 送信時刻 |
招待フローとの関係
招待フロー の Step 5(アラートなしで招待手続き)で、候補者に本フォームを 案内する。候補者が同意して送信すると招待リンクが自動でメール配信される (従来の Notion フォーム + Notion オートメーションを内製アプリへ置き換えたもの)。配信する 招待リンクは事前に tied-workspace-admin で設定しておく。
運用
開発・デプロイ・環境変数・メール送信の設定は 運用 / デプロイ を参照。