結論:3通のメール自動化で登録→有料転換を仕組み化できる
Resend + Supabase Database Webhook + Supabase Cron Jobsを使えば、登録直後から6日間のメールシーケンスを全自動化できます。実装工数は約4時間、月固定費は¥0(月3,000通以内)。
完成後の動作フロー:
ユーザー登録(Supabase Auth)
↓
Database Webhook → /api/email/welcome
↓
Resend API → Day 0 ウェルカムメール
↓
email_sequences テーブルに Day3/Day6 予約を記録
↓
Supabase Cron(毎時実行)→ 対象レコードを処理
↓
Resend API → Day 3 / Day 6 メール送信
Step 1: Resend初期設定 + メールテンプレート
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
const templates: Record<EmailSequenceDay, (name: string) => { subject: string; html: string }> = {
0: (name) => ({
subject: `${name}さん、登録ありがとうございます — まず試してほしい3つのこと`,
html: `
<p>${name}さん、こんにちは。masatoman です。</p>
<h2>まず試してほしい3つのこと</h2>
<ol>
<li>ダッシュボードからプロジェクトを作成する</li>
<li>Ep.01「スキル入門」を読む(15分)</li>
<li>Discordコミュニティに入る</li>
</ol>
<p>3日後に「実践でつまずく5つのポイント」をお送りします。</p>
`,
}),
3: (name) => ({
subject: `${name}さんへ:Claude Codeで時間を削るための3つのコツ`,
html: `
<p>${name}さん、登録から3日が経ちました。</p>
<h2>実践でつまずく3つのポイント</h2>
<ol>
<li>CLAUDE.md が育っていない</li>
<li>コンテキストを使いすぎる(サブエージェント分割でコスト半減)</li>
<li>Hooks を使っていない</li>
</ol>
<p><a href="https://masatoman.net/articles/claude-code-lab-ep01-skills-intro-2026">→ 詳細記事を読む</a></p>
`,
}),
6: (name) => ({
subject: `${name}さんへ:Standardプランで何が変わるか(正直に話します)`,
html: `
<p>${name}さん、登録から6日目です。</p>
<p>Standardプラン(¥1,980/月)では全エピソードの実装コードをそのまま使えます。</p>
<p><a href="https://masatoman.net/pricing">→ プランの詳細を見る</a></p>
`,
}),
}
export async function sendSequenceEmail({
to,
day,
userName = 'あなた',
}: {
to: string
day: EmailSequenceDay
userName?: string
}) {
const template = templates[day](userName)
return resend.emails.send({
from: 'masatoman <hello@masatoman.net>',
to,
subject: template.subject,
html: template.html,
})
}
Step 2: 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()
);
-- 重複送信防止
ALTER TABLE email_sequences ADD CONSTRAINT unique_user_day UNIQUE (user_id, day);
-- Cronが高速に未送信レコードを引けるインデックス
CREATE INDEX idx_email_sequences_pending ON email_sequences(send_at)
WHERE sent_at IS NULL;
Step 3: Webhook受信API(Day 0送信 + Day3/Day6予約)
Supabaseダッシュボード → Database → Webhooks で auth.users の INSERT をトリガーに設定。
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] ?? 'あなた'
// Day 0: 即時送信
await sendSequenceEmail({ to: user.email, day: 0, userName })
// Day 3 / Day 6: 予約記録
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: 予約メール送信API + Supabase Cron設定
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) => {
// 楽観的ロック: 二重送信防止
const { count } = await supabase
.from('email_sequences')
.update({ sent_at: new Date().toISOString() })
.eq('id', mail.id)
.is('sent_at', null)
.select('id', { count: 'exact', head: true })
if (!count) return // 既に別インスタンスが送信済み
await sendSequenceEmail({
to: mail.user_email,
day: mail.day as EmailSequenceDay,
userName: mail.user_name ?? 'あなた',
})
})
)
return NextResponse.json({ sent: results.filter((r) => r.status === 'fulfilled').length })
}
Supabase pg_cron設定(毎時実行):
SELECT cron.schedule(
'send-email-sequences',
'0 * * * *',
$$
SELECT net.http_post(
url := 'https://your-domain.com/api/email/send-due',
headers := jsonb_build_object(
'Authorization', 'Bearer ' || current_setting('app.webhook_secret')
)
)
$$
);
期待できる効果(業界ベンチマーク試算)
SaaS業界の一般的なメールシーケンス効果(Intercom / ChartMogul 等の公開データより):
| 指標 | メール未実装 | 3通シーケンス実装時 |
|---|---|---|
| 30日以内有料転換率(業界目安) | 〜1% | 3〜8% |
| 月50人登録時の試算転換人数 | 約0.5人 | 1.5〜4人 |
| MRR試算貢献(¥1,980プラン想定) | 約¥990 | ¥2,970〜¥7,920 |
※ 上記は業界平均からの試算値です。筆者のプロジェクト(Claude Crew Lab)は現在Free MVPを運用中でベースライン計測中のため、実測値は節目で公開します。
Day 6のプラン比較メールが有効とされる最大の理由は「ROI提示」です。CTAの前に「月¥1,980でClaude Code費用を回収できるか?」という問いを置くことで、読者が自分ゴトとして判断できるようになります。
詳細な実装解説と収益導線の設計は
masatoman.net の有料記事では、リテンションメール(解約予告ユーザー向け)・行動ベーストリガーメールの実装コードも公開しています(¥500)。