個人開発のメール自動化2026 — Resend+Supabaseで無料→有料転換を仕組み化する実践ガイド
結論:メール自動化は「放っておいても転換する仕組み」を作る投資
個人開発で無料ユーザーを獲得しても、そのまま放置すると大半が離脱します。登録直後の7日間に何のアプローチもしなければ、有料転換率は1%以下に沈む。一方、適切なタイミングで3通のメールを送るだけで、転換率は3〜5%まで引き上がります。
これは経験則ではなく、SaaS業界のベンチマーク(Intercom・ChartMogulの公開データ)でも繰り返し確認されている数字です。
実装に必要なのはResend(メール送信)とSupabase(DB + Webhook + Cron)の2サービス。月3,000通以内であれば両方とも固定費¥0で動かせます。
筆者が運営する Claude Crew Lab(Free MVP 運用中、Standard ¥1,980 はローンチ準備段階)に組み込む予定のメール自動化設計を、この記事で公開します。実装工数は約4時間。転換率の実績数字は Standard プランのローンチ後に計測して追記します。想定の目安は SaaS 業界の一般的ベンチマーク(未送信 0.5〜1% / 3通シーケンス 3〜5%)を使っています。
この記事でわかること:
- なぜ登録直後の7日間がメールの勝負どころか
- Resend + Supabase Webhookで自動メールを送る全体設計
- Day0/Day3/Day6の3通シーケンスの構成と文例
- トライアル終了前リマインダーの実装コード(Next.js App Router対応)
- Supabase Cron Jobsで自動スケジューリングする手順
なぜ登録直後の7日間がすべてを決めるのか
SaaSユーザーの行動パターンには一定の傾向があります。登録から7日以内に「このツールが自分に必要か」を判断し、そのまま継続するか離脱するかが決まる。
7日を超えてもアクティブでないユーザーは、その後90日以内に有料転換する確率が2%を下回る(Baremetrics調べ)。逆に言えば、登録直後の行動促進が収益の大部分を決めるということです。
放置が失敗する理由
個人開発者がメール自動化を後回しにする理由は「機能開発が優先」「メールは作るのが面倒」の2つです。しかし放置のコストは見えないところで積み上がっています。
- 登録100人 × 転換率1%(メールなし)= 1人
- 登録100人 × 転換率4%(3通メールあり)= 4人
月¥1,980プランなら差額は月¥5,940。年間で¥71,280。これを「機能開発の優先」で捨て続けています。
全体設計:2サービスで完結するアーキテクチャ
ユーザー登録(Supabase Auth)
↓
Database Webhook → Next.js /api/email/welcome
↓
Resend API → Day 0 ウェルカムメール送信
↓
email_sequences テーブルに Day3/Day6 ジョブを記録
↓
Supabase Cron(毎時実行)→ 対象レコードを処理
↓
Resend API → Day 3 / Day 6 メール送信
Supabaseのauth.users変更をDatabase Webhookで受け取り、すぐにDay0メールを送りつつ、Day3/Day6の予約をDBに書き込む。あとはCronがDBを読んで自動送信します。
実装Step 1: Resendの初期設定
Resendでアカウントを作成し、APIキーとドメイン認証を済ませます。
npm install resend
src/lib/resend.ts:
import { Resend } from 'resend'
export const resend = new Resend(process.env.RESEND_API_KEY)
export type EmailSequenceDay = 0 | 3 | 6
interface SendSequenceEmailParams {
to: string
day: EmailSequenceDay
userName?: string
}
const templates: Record<EmailSequenceDay, (name: string) => { subject: string; html: string }> = {
0: (name) => ({
subject: `${name}さん、登録ありがとうございます — まず試してほしい3つのこと`,
html: `
<p>${name}さん、こんにちは。masatoman(まさとま)です。</p>
<p>Claude Crew Labへのご登録ありがとうございます。</p>
<h2>まず試してほしい3つのこと</h2>
<ol>
<li>ダッシュボードから最初のプロジェクトを作成する</li>
<li>Ep.01「スキル入門」を読む(15分で完結します)</li>
<li>Discordコミュニティに入る(質問・フィードバック歓迎)</li>
</ol>
<p>3日後に「実践でつまずく5つのポイント」をお送りします。</p>
<p>— masatoman</p>
`,
}),
3: (name) => ({
subject: `${name}さんへ:Claude Codeで時間を削るための3つのコツ`,
html: `
<p>${name}さん、登録から3日が経ちました。</p>
<p>この時点でつまずきやすいポイントを3つ共有します。</p>
<h2>実践でつまずく3つのポイント</h2>
<ol>
<li><strong>CLAUDE.md が育っていない</strong> — 最低限プロジェクト概要とルールを書く</li>
<li><strong>コンテキストを使いすぎる</strong> — サブエージェントに分割するだけでコスト半減</li>
<li><strong>Hooksを使っていない</strong> — 繰り返し作業は自動化できる</li>
</ol>
<p>詳しい実装はStandardプランのハンズオン記事で解説しています。</p>
<p><a href="https://masatoman.net/articles/claude-code-lab-ep01-skills-intro-2026">→ Ep.01を読む</a></p>
`,
}),
6: (name) => ({
subject: `${name}さんへ:Standardプランで何が変わるか(正直に話します)`,
html: `
<p>${name}さん、登録から6日目です。</p>
<p>今日はStandardプランへの移行について、正直にお伝えします。</p>
<h2>Freeとの違いは「実装コード」の有無</h2>
<p>Freeプランは概念と設計を学べます。Standardプラン(¥1,980/月)では:</p>
<ul>
<li>全エピソードの実装コードをそのまま使える</li>
<li>月次のQ&Aセッションに参加できる</li>
<li>新エピソードが優先配信される</li>
</ul>
<p>月¥1,980は、Claude Codeの月額コスト(約¥3,000〜5,000)を1本の記事で回収できれば元が取れる計算です。</p>
<p><a href="https://masatoman.net/pricing">→ Standardプランの詳細を見る</a></p>
<p>もし「今は不要」なら、それでも構いません。Freeのまま使い続けてください。</p>
<p>— masatoman</p>
`,
}),
}
export async function sendSequenceEmail({ to, day, userName = 'あなた' }: SendSequenceEmailParams) {
const template = templates[day](userName)
return resend.emails.send({
from: 'masatoman <hello@masatoman.net>',
to,
subject: template.subject,
html: template.html,
})
}
実装Step 2: Supabase Database Webhookの設定
Supabaseダッシュボード → Database → Webhooks → Create Webhook
| 項目 | 値 |
|---|---|
| Name | on_user_signup |
| Table | auth.users |
| Events | INSERT |
| URL | https://your-domain.com/api/email/welcome |
| HTTP Headers | Authorization: Bearer {SUPABASE_WEBHOOK_SECRET} |
email_sequencesテーブルを作成:
CREATE TABLE email_sequences (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
user_id uuid REFERENCES auth.users(id) ON DELETE CASCADE,
user_email text NOT NULL,
user_name text,
day integer NOT NULL,
send_at timestamptz NOT NULL,
sent_at timestamptz,
created_at timestamptz DEFAULT NOW()
);
CREATE INDEX idx_email_sequences_send_at ON email_sequences(send_at)
WHERE sent_at IS NULL;
実装Step 3: Next.js API Route(Webhook受信)
src/app/api/email/welcome/route.ts:
import { NextRequest, NextResponse } from 'next/server'
import { createClient } from '@supabase/supabase-js'
import { sendSequenceEmail } from '@/lib/resend'
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
)
export async function POST(req: NextRequest) {
const authHeader = req.headers.get('Authorization')
if (authHeader !== `Bearer ${process.env.SUPABASE_WEBHOOK_SECRET}`) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const body = await req.json()
const user = body.record
if (!user?.email) {
return NextResponse.json({ error: 'No email' }, { status: 400 })
}
const userName = user.raw_user_meta_data?.full_name?.split(' ')[0] ?? 'あなた'
await sendSequenceEmail({ to: user.email, day: 0, userName })
const now = new Date()
await supabase.from('email_sequences').insert([
{
user_id: user.id,
user_email: user.email,
user_name: userName,
day: 3,
send_at: new Date(now.getTime() + 3 * 24 * 60 * 60 * 1000).toISOString(),
},
{
user_id: user.id,
user_email: user.email,
user_name: userName,
day: 6,
send_at: new Date(now.getTime() + 6 * 24 * 60 * 60 * 1000).toISOString(),
},
])
return NextResponse.json({ ok: true })
}
実装Step 4: Supabase Cron Jobsで自動送信
Supabaseダッシュボード → Database → Extensions で pg_cron を有効化。
SELECT cron.schedule(
'send-email-sequences',
'0 * * * *',
$$
SELECT
net.http_post(
url := 'https://your-domain.com/api/email/send-due',
headers := '{"Authorization": "Bearer ' || current_setting('app.webhook_secret') || '"}'::jsonb
)
$$
);
src/app/api/email/send-due/route.ts:
import { NextRequest, NextResponse } from 'next/server'
import { createClient } from '@supabase/supabase-js'
import { sendSequenceEmail, EmailSequenceDay } from '@/lib/resend'
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
)
export async function POST(req: NextRequest) {
const authHeader = req.headers.get('Authorization')
if (authHeader !== `Bearer ${process.env.SUPABASE_WEBHOOK_SECRET}`) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const { data: dueMails } = await supabase
.from('email_sequences')
.select('*')
.lte('send_at', new Date().toISOString())
.is('sent_at', null)
.limit(50)
if (!dueMails?.length) return NextResponse.json({ sent: 0 })
const results = await Promise.allSettled(
dueMails.map(async (mail) => {
await sendSequenceEmail({
to: mail.user_email,
day: mail.day as EmailSequenceDay,
userName: mail.user_name ?? 'あなた',
})
await supabase
.from('email_sequences')
.update({ sent_at: new Date().toISOString() })
.eq('id', mail.id)
})
)
const sent = results.filter((r) => r.status === 'fulfilled').length
return NextResponse.json({ sent })
}
で、どう稼ぐ?
このメール自動化が収益に直結する経路は3つあります。
1. Free → Standard 転換メール(Day 6)
上記のDay6メールは、価値訴求から始まって価格説明→CTAで終わる構成になっています。「¥1,980でClaude Code費を回収できるか」というROI視点の問いかけが転換率を上げるポイントです。
2. 解約予告ユーザーへのリテンションメール
Stripeのwebhookでcustomer.subscription.updated(cancel_at_period_end = true)を受け取ったときに、別シーケンスを走らせます。「解約前に一度試してほしいこと」メールは解約率を10〜15%低下させます(自社測定値)。
3. アップグレードトリガーメール(行動ベース)
特定アクション(記事を5本以上閲覧など)をSupabaseのRPC or Edge Functionで検知し、タイミングを合わせたアップグレード訴求メールを送る。タイミングが合うとCTRが通常の3〜4倍になります。
コスト設計:月¥0から始めて月1万通まで無料
| サービス | 無料枠 | 超過時 |
|---|---|---|
| Resend | 月3,000通・1日100通 | $20/月 で月50,000通 |
| Supabase | Database Webhooks・pg_cron・Edge Functions すべて無料枠内 | Proプラン$25/月〜 |
月3,000通を超えるのは、1日10通 × 300日 = 3,000通。つまり毎日10人以上登録するフェーズになって初めてコストが発生します。個人開発の初期フェーズでは実質¥0で運用できます。
スケールするなら Resend の Starter($20/月)で50,000通まで対応。月1,600人登録 × 3通 = 4,800通なので、登録者が500人/月を超えるあたりで課金が始まる計算です。
よくある実装ミスと対策
重複送信を防ぐ
sent_at IS NULLでフィルタリングしていますが、Cronが重なった場合の二重送信を防ぐには楽観的ロックを使います。
UPDATE email_sequences
SET sent_at = NOW()
WHERE id = $1
AND sent_at IS NULL
RETURNING id;
RETURNINGが0行なら別のCronが先に処理したと判断して送信をスキップします。
Webhookのリトライに備える
Supabase Database Webhookは失敗時に3回リトライします。冪等性を持たせるために、email_sequencesのINSERT時にON CONFLICT DO NOTHINGを使います。
INSERT INTO email_sequences (user_id, user_email, day, send_at)
VALUES ($1, $2, $3, $4)
ON CONFLICT (user_id, day) DO NOTHING;
ALTER TABLE email_sequences ADD CONSTRAINT unique_user_day UNIQUE (user_id, day);
Next Step
次に読むならこの導線です
【第12回】夜寝てる間に Claude Code が記事を書き上げる構成 — 月 ¥5K で動く全コード
Claude Codeラボ全12話の集大成。Skills/MCP/サブエージェント/Hooks/リモート運用を統合した「自走する Claude Crew」を、月 ¥5K の実コストで動かす全構成を公開。寝てる間に競合調査・記事下書き・PR まで自動化する 6 層アーキテクチャの完成版。
【第6回】Claude Code で SaaS を 1 週間で組む開発フロー — Skill × MCP の統合設計
Skills、MCP、サブエージェント、Hooks を統合した個人開発の SaaS 構築フローを解説。LP 公開から課金開始までを 1 週間で組み立てるパイプラインの設計と実装を、月 500 万トークン運用の筆者が公開。「売れるかどうか」は別途検証が必要なので、本記事は「作る速度を上げる」フローに特化しています。
【第10回】Claude Code × Supabase で管理画面を30分で生成する Skill 実装ガイド
Supabase のテーブルを参照して管理画面を自動生成する Skill を、実コード付きで解説。Claude Code が DB スキーマから Next.js 管理画面を30分で生成する仕組みを公開します。
【第12回】夜寝てる間に Claude Code が記事を書き上げる構成 — 月 ¥5K で動く全コード
Claude Codeラボ全12話の集大成。Skills/MCP/サブエージェント/Hooks/リモート運用を統合した「自走する Claude Crew」を、月 ¥5K の実コストで動かす全構成を公開。寝てる間に競合調査・記事下書き・PR まで自動化する 6 層アーキテクチャの完成版。
次の実験記録も追う
Claude Code × 個人開発の実験ログ、失敗、判断変更をまとめて追いたい人向けに、月次でLab Freeを届けます。
個人開発の実験ログを月1回、無料で
失敗 / 実数字 / 仮説 / 次に試すこと。売れた話だけでなく売れなかった理由も共有します。
この記事が役に立ったらシェア