3日でBtoBマッチングSaaSを本番公開した話—SES 企業向けサービス「SESAMI」の設計・実装・運用記

目次

はじめに

株式会社 Root on の藤森です。この一週間、SES 業界向けの企業マッチングサービス SESAMI(セサミ) を、設計・実装・本番公開まで実稼働 3 日で立ち上げました。既に ses-ami.com で稼働しており、ドメイン取得から法人本人確認つきの Stripe サブスク決済有効化までひと通り動いている状態です。

小さなサービスではありますが、「いまの時代に本気で薄く小さく作るとここまで短く安くなる」という感触が得られた開発だったので、記録として残しておきます。SES 業界の方にとっては同業プレイヤーの小さな一手として、Web エンジニアや PM の方にとってはスタックの選び方の参考として、どこか使える部分があれば嬉しいです。

なぜ作ったのか

SES 業界にいる人なら誰もが抱える悩みのひとつに「BP(ビジネスパートナー)開拓の非効率さ」があります。案件や要員の情報を回すには信頼できる相手を増やす必要があるのに、その相手は結局、電話・メール・Chatwork での地道な一対一開拓でしか増えていかない。名刺交換会、知人の紹介、古くから続く取引先——どれも大事ですが、数を増やそうとした瞬間に時間が足りなくなります。

「検索して、気になる相手に『つながりたい』と一言送り、承認されたら個別のチャットルームが自動で立ち上がる」。これだけ切り出してシンプルに提供するプラットフォームがあれば、少なくとも開拓のオーバーヘッドは激減するのではないか。そんな仮説のもと、まずは自分の手を動かしてみようと思いました。

SNS 的な軽さ、商談ツール的な仕事感、そしてクローズドな安心感。この三つを一本に束ねる、ちょうどいい温度感のサービスが SES 業界には不足していると感じており、それを埋めるプロダクトを目指しました。

PM として最初にやったこと——「何を作らないか」を決める

3 日で本番まで持っていくためには、作る機能を決めるより「作らない機能」を決めるほうが大事でした。機能要件を紙に書き出し、優先度を付けてから、MVP に入れない と決めた項目を明示していきました。

MVP に含めたのは以下の機能群です。

  • 会員登録とメール認証、パスワードリセット
  • 企業プロフィールの登録・編集(ロゴ画像、代表者、得意技術、業界、対応エリア、エンジニア数、案件数など)
  • 企業一覧・検索(技術・業界・エリアを横断したキーワード検索)
  • つながりリクエストの送信・承認・管理、日次上限チェック
  • 承認後の 1 対 1 チャット、既読管理、メッセージ取り消し
  • 通知(ヘッダーバッジとメール)
  • サマリーダッシュボード(新着リクエスト・未読メッセージ・つながり企業数)
  • Stripe サブスク課金(Free 3 件 / 日、Pro 月額 1,000 円で 10 件 / 日)
  • 管理画面(企業審査、KPI、設定)
  • 特商法・利用規約・プライバシーポリシー
  • お問い合わせフォーム

逆に、あえて外したのは次のようなものです。

  • WebSocket や Durable Objects によるチャットのリアルタイム化。5 秒ポーリングで擬似リアルタイムにする
  • 画像のクラウドストレージへの切り出し。ロゴは当面 Base64 データ URL のまま DB に格納
  • きめ細かい権限管理。管理者は環境変数の許可リスト方式でいい
  • 企業レビュー機能。DB スキーマだけ先に用意しておき、画面は Phase 2

これらは「あると良いが無くても初日の体験を壊さない」機能です。最初から全部盛り込もうとすると 3 日では絶対に終わらない。足りないところは運用しながら埋めていけば良く、実利用者の声を聞いてからのほうが筋のいい実装ができます。

技術スタックの選定と、その理由

今回はスタック選びに時間をかけませんでした。この 2〜3 年の潮流をきちんと追っていれば、小さな BtoB SaaS の正解セットはある程度見えているからです。結果として採用したのは次の構成です。

フロントエンド:Cloudflare Pages 上の React + Vite

  • React 19 + Vite 6。業界のデファクトで、pnpm dev の起動時間はほぼゼロ、ホットリロードも即応します。
  • TailwindCSS 4@theme ブロックでデザイントークンを宣言し、コード中ではユーティリティクラスだけで完結する運用です。CSS-in-JS は採用していません。
  • TanStack Query でサーバ状態、Zustand でクライアント状態を管理。Redux 系の重さは不要でした。
  • React Router でルーティング。状態管理の迷いは一切なし。

バックエンド:Hono on Cloudflare Workers

バックエンドは Hono を採用しました。Express よりも型が強く、記述量も少なく、何より Cloudflare Workers のエッジランタイム との相性が最良です。コールドスタートがほぼゼロで、30ms 前後の応答速度が標準的に出ます。AWS Lambda で悩まされる起動遅延の話は、ここには無縁です。

Drizzle ORM を選んだのも、TypeScript ファーストで型安全なスキーマ定義ができるためです。Prisma と迷いましたが、ランタイムが軽量で Workers と相性のいい Drizzle の方が今回のユースケースには合っていました。PostgreSQL ドライバは Workers 対応を明言している postgres.js を利用しています。

データベース:PlanetScale PostgreSQL Single Node(月 5 ドル)

データベースは PlanetScale の PostgreSQL Single Node プラン を選択。月額 5 ドルで、PgBouncer 込みの PostgreSQL 18.3 環境が立ち上がります。

当初 CLAUDE.md には「PlanetScale」と書いていましたが、PostgreSQL のサポートが実際に使える状態で手に入るなら、MySQL にせずに PostgreSQL で統一した方が jsonb の柔軟性を享受できます。一覧検索では jsonb_array_elements_textILIKE を組み合わせて、技術スキル・業界・エリアを横断したキーワード検索を一本のクエリで実装しました。

決済:Stripe

サブスク課金は Stripe。Checkout Session と Customer Portal を使えば、アップグレード画面も解約画面も自前で作る必要がありません。Webhook を customer.subscription.created/updated/deletedinvoice.payment_failedcheckout.session.completed の 5 つに絞り、DB の plan カラムを機械的に同期させています。

ハマったのは、Cloudflare Workers では Node 互換の同期 constructEvent が使えず、必ず constructEventAsync を使う必要があること。SubtleCrypto で署名検証する都合です。ドキュメントを読めば書いてあるのですが、最初の Webhook 組み込み時に 30 分溶かしました。

メール:Resend(送信)+ Cloudflare Email Routing(受信)

メール送信は Resend。無料枠で月 3,000 通まで送れます。ses-ami.com のドメイン認証(SPF / DKIM)を済ませ、東京リージョン配信にしてあります。一方、受信用の contact@ses-ami.comCloudflare Email Routing で個人受信アドレスへ転送する構成。こちらも無料で、MX の設定も数クリックで完結しました。

CMS:microCMS

ブログ記事は microCMS。スキーマファーストの国産ヘッドレス CMS で、JP の事業向きです。記事投稿から実画面反映まで 10 秒くらい。「下書きとは別に isProductionRelease という真偽値を立てて、本番からは真の記事だけ引く」ルールにすることで、ステージング的な挙動も自前で実現しています。

こちらについては、下記の記事で詳しく書いています。

ドメイン:Cloudflare Registrar

ses-ami.comCloudflare Registrar で取得しました。WHOIS プライバシー無料、DNS も同ダッシュボードで管理でき、年間 10 ドル前後。ドメイン事業者に無駄なマークアップを払う時代は終わりました。

モノレポ:Turborepo + pnpm workspaces

packages/web(フロント)、packages/api(Workers)、packages/shared(共有の型・Zod バリデータ)の 3 パッケージを Turborepo で束ねています。型定義を @ses-matching/shared に置いて両方から参照させることで、「API のレスポンス形が変わってフロントが壊れる」古典的事故を排除しました。

言語・型安全:TypeScript strict + Zod 検証

全レイヤー TypeScript の strict モード。リクエストとレスポンスは Zod で検証し、URL パラメータも含めて「受け取るすべての外部入力を schema に通す」ルールを徹底しました。これが後から効いたのは、手を入れるたびに「どこを触ると何が壊れるか」が型で見えること。大規模になってもメンテ不能にならない自信があります。

3 日間の流れ

Day 1 はスキャフォールディングとコア機能。認証、プロフィール、企業一覧、つながりリクエスト、チャットまでを一気に繋ぎました。この日は本番デプロイを意識せず、ひたすらローカルで動く体験を固める時間。

Day 2 は通知、ダッシュボード、管理画面、Stripe のテストモード組み込み。管理者機能は環境変数ホワイトリストで十分と割り切り、DB にフラグを持たせる設計は避けました。テーブル追加は app_settings (key, value) の 1 件のみ。スキーマ変更は最小限に留めます。

Day 3 が最も濃密でした。午前に ses-ami.com の取得と DNS 設定、Cloudflare Workers と Pages の初回デプロイ。PlanetScale 本番 DB の接続とマイグレーション適用。CORS を本番ドメインに合わせて更新し、フロントに VITE_API_BASE_URL を通して api.ses-ami.com を指せるように仕込み。午後に Resend のドメイン認証、Stripe Webhook 登録、特商法・利用規約・プライバシーポリシーの実情報差し替え。夕方に実カードでの決済テストを通して、Pro プランが DB に反映されることを目視確認。夜にブログ(microCMS)との疎通確認と、品質改善としてプロフィール保存バグの修正、カードレイアウトのリッチ化などをこなしました。

ちなみにこの「Workers の Date が起動直後に 1970-01-01 を返す」仕様にハマって、設立年フィールドがどうしても 1970 年以上で弾かれるバグが出ました。サイドチャネル攻撃対策のためです。Zod の .max(new Date().getFullYear()) を直書きするとスキーマのビルド時に評価されて固定化される。.refine() 内に移動することでリクエスト時評価に変えて解決しました。こういう「聞いたことはあるが一度は踏まないと忘れる」罠が、何箇所か。

月額コストの内訳

項目月額
ドメイン (ses-ami.com)約 $1(年 $10〜12 を按分)
Cloudflare Pages / Workers / DNS / Email Routing$0(無料枠)
PlanetScale PostgreSQL Single Node$5
Resend(3,000 通/月まで)$0
microCMS(無料プラン)$0
Stripe売上比例手数料のみ(決済が無ければゼロ)
合計約 $6/月

BtoB SaaS を本番稼働させて、月に 1,000 円行かない。売上比例の Stripe 手数料だけはスケールに応じて変動しますが、固定費はこれだけです。2015 年頃の感覚だと、VPS 借りて諸々組み込んで月 5,000 円スタートが普通でした。10 年で価格構造が完全に変わっています。

品質をどう担保したか

「3 日で作った」と聞くと「どうせ雑なんでしょう?」と思われがちですが、品質には最初から気を配って進めました。

TypeScript の strict モードを前提にし、あらゆる入出力を Zod で検証。API のエラーレスポンス構造も { error: { code, message } } で統一し、フロントは code で分岐できるように設計しました。

各機能のリリースは feature ブランチ → PR → レビュー → main マージのサイクルを徹底。コードレビューには CodeRabbit(AI コードレビュー)を導入し、GitHub App 連携で PR が立つたびに自動で診断が走るように。「CodeRabbit から受けた指摘は CLAUDE.md に恒久的に蓄積し、同じ指摘が二度と来ないようにする」 というルールを最初に決めておき、セキュリティ・バリデーション・DB・UI の 5 カテゴリに分類して知見を残しています。Origin ヘッダのホワイトリスト検証、DELETE エンドポイントの Zod 化、UI と API の境界値判定の不整合回避——どれも運用が長くなれば必ず引っかかる種類の指摘で、これを文書として残すだけで未来の自分(とチーム)を救えます。

QA フェーズでは、本番サイトをひと通り触りながら気になる点を洗い出し、1 つずつ PR を切って修正しました。企業プロフィールが設立年で弾かれて保存できないバグ、紹介文の改行が消えるバグ、一覧カードが情報不足で見つけにくいフィードバックなど、計 10 本以上の小さな改善 PR を積んで品質を上げていきました。

作ってみて感じたこと

エッジ系の技術スタックは、もはや「尖った選択」ではなく「デフォルト」です。Cloudflare Workers のコールドスタートゼロ、グローバル配信、DDoS 耐性、無料枠の広さ。AWS を使うべき理由が明確にない限り、新規プロジェクトで AWS を選ぶのは逆に珍しいフェーズに入ってきていると感じます。

Drizzle + PlanetScale PostgreSQL の組み合わせも相当よかったです。スキーマ変更が怖くない、マイグレーションが追いやすい、本番 DB への適用も drizzle-kit migrate 一発。ORM を敬遠している方は一度触ってみてください。

モノレポ + 共有型は事故を減らします。「API を直したらフロントもすぐ型エラーで教えてくれる」のは気持ちがいい。

開発速度はツール選びで 3 倍変わる。個別のツールが 1.1 倍の高速化だとしても、フロント・バック・DB・デプロイ・決済・メール・CMS・ドメイン・DNS のそれぞれが 1.1 倍になれば、全体では 1.1^9 ≈ 2.4 倍になります。ひとつひとつの選択は些細でも、積み重ねれば「3 日で立ち上げ」は現実的な数字になります。

これから

Phase 2 としては、以下を順次進める予定です。

  • チャットのリアルタイム化(5 秒ポーリング → Cloudflare Durable Objects)
  • ロゴ画像の Cloudflare R2 バケットへの移行(DB を軽く保つ)
  • GitHub Actions による CI/CD(typecheck の PR ブロック等)

機能追加ではなく「運用品質を上げる」フェーズです。スケールが必要になったタイミングで手を打てるよう、計測とモニタリングも整えていきます。

最後に

「3 日で作る」は方法論ではなく結果です。やらないことを決める勇気、枯れていて小さなスタックを選ぶ判断、運用まで見通した初日からの設計。この三つが揃うと、個人や小さなチームでも BtoB SaaS をこのスピードで世に出せる時代になっています。

BP 開拓でお困りの SES 企業の皆様、https://ses-ami.com で SESAMI をお試しいただけます。無料プランから始められますので、ぜひ気軽に登録してみてください。

そして、「自社のサービスをこういう薄い構成で立ち上げ直したい」「プロダクト立ち上げを一気通貫で設計できる PM を探している」という方がいらっしゃれば、Root on までお気軽にお声がけください。


Root on では、プロダクト戦略から設計・実装・運用までワンストップでご支援しています。

目次