요약
Supabase 확인 메일은 제목·설명·버튼 문구를 한국어로 바꾸고 인증 링크 변수는 유지합니다. 제공 HTML을 로컬에서 열어 계정·발송 없이 미리 볼 수 있습니다. 실제 메일 도착·인증·리디렉션 성공은 별도로 확인합니다.
이 기술만 따로 연습하기
이메일 템플릿과 앱이 포함된 ZIP에서 email-template/starter/confirmation.html을 별도 파일로 열면 계정이나 메일 발송 없이 본문 실습을 시작할 수 있습니다.
- 영문 제목·설명·버튼 문구를 한국어로 바꿉니다. a 요소의 href=”{{ .ConfirmationURL }}”은 그대로 유지합니다.
- 저장한 HTML을 브라우저에서 열어 제목, 설명, 확인 링크 순서가 읽히는지 확인합니다.
- email-template/complete/confirmation.html과 비교해 버튼 표시 문구와 실제 링크 변수를 구별합니다. 링크를 일반 사이트 주소로 바꾸면 인증 토큰 연결이 사라지는 이유를 한 문장으로 적습니다.
기대 결과: 한국어 메일 본문 파일이 결과물입니다. 로컬 HTML에서 템플릿 변수가 실제 인증 URL로 치환되지 않는 것은 정상이며, 이 단계는 이메일 확인 성공 테스트가 아닙니다.
자가 확인·변형: 버튼을 “가입 확인하기”에서 “이메일 주소 확인”으로 바꾸어도 링크 변수는 유지되어야 합니다. 실제 메일 전송·인증·리디렉션은 하단의 개발 프로젝트 적용 단계에서 별도로 확인합니다.
Supabase 가입 확인 메일을 한국어로 바꾸려면 무엇을 유지해야 하나요?
Confirm signup 템플릿의 제목·설명·버튼 문구를 한국어로 바꾸고, 버튼의 인증 링크 변수 {{ .ConfirmationURL }}는 유지합니다. 메일 수신, 이메일 확인, 원하는 페이지로 이동은 각각 따로 확인해야 합니다.
| 증상 | 먼저 확인할 것 | 다음 확인 |
|---|---|---|
| 템플릿을 수정할 수 없음 | 프로젝트 생성 시점·플랜·기본 SMTP 사용 여부 | 무료 플랜 제한과 custom SMTP 조건 |
| 버튼을 눌러도 인증이 진행되지 않음 | 버튼 링크의 {{ .ConfirmationURL }} 유지 여부 |
해당 사용자의 이메일 확인 상태 |
| 인증 후 원하지 않는 주소로 이동 | Site URL·Redirect URLs·emailRedirectTo |
로컬·운영 URL 설정 |
| 메일은 왔지만 로그인 결과가 예상과 다름 | Confirm email 설정과 링크 클릭 여부 | 인증 완료와 세션 상태를 각각 확인 |
적용 대상은 Supabase Auth의 이메일 확인 템플릿입니다. 공식 이메일 템플릿 문서와 리디렉션 문서를 함께 확인하세요. 본문의 HTML 미리보기만으로 실제 메일 발송·인증 성공이 검증되지는 않습니다.
이메일 회원가입에서 Confirm email을 사용하면 흐름은 다음과 같습니다.
사용자 signUp
↓
Supabase 사용자 생성
↓
확인 메일 발송
↓
사용자가 링크 클릭
↓
이메일 확인
↓
허용된 URL로 이동
따라서 메일 본문을 수정할 때도 인증 링크가 사라지면 안 됩니다.
문구는 UI이지만 링크 변수는 인증 기능의 일부입니다.
Email Provider와 Confirm email 확인
Supabase Dashboard에서 프로젝트의 Authentication 설정을 엽니다.
Dashboard UI 명칭은 업데이트될 수 있으므로 발행 시점에 실제 화면을 확인합니다.
확인할 핵심은 다음입니다.
Email Provider 활성화
Confirm email 사용 여부
Site URL
Redirect URLs
Email Templates
Confirm email이 활성화되어 있다면 3회차의 signUp 성공 후 session이 null일 수 있습니다.
이것은 회원가입 실패가 아니라 이메일 확인을 기다리는 상태일 수 있습니다.
2026년 무료 플랜 템플릿 제한
2026년 6월 3일 Supabase는 무료 플랜의 기본 이메일 발송 인프라를 이용하는 신규 프로젝트에서 Auth 이메일 템플릿 커스터마이징을 제한했습니다.
피싱 악용을 줄이기 위한 변경입니다.
따라서 한국어 템플릿을 수정하려는데 Dashboard에서 편집이 제한된다면 코드 오류로 단정하지 않습니다.
프로젝트 생성 시점, 플랜, 이메일 발송 방식을 확인합니다.
공식 변경 안내 기준으로 한국어 등 사용자 정의 템플릿이 필요한 경우 다음 선택지를 검토합니다.
유료 플랜
또는
직접 구성한 custom SMTP
플랜 정책은 바뀔 수 있으므로 실제 발행일과 적용 시점에는 공식 Changelog와 Dashboard를 다시 확인합니다.
수정 전 템플릿
학습용 시작 파일을 다음처럼 둡니다.
starter/confirmation.html:
<h2>Confirm your signup</h2>
<p>Follow this link to confirm your user:</p>
<p>
<a href="{{ .ConfirmationURL }}">
Confirm your mail
</a>
</p>
실제 Dashboard의 기본 템플릿은 프로젝트 시점에 따라 다를 수 있습니다.
중요한 것은 문구가 아니라 확인 링크에 연결된 템플릿 변수를 유지하는 것입니다.
한국어 템플릿으로 변경
complete/confirmation.html:
<h2>TaskFlow 회원가입을 확인해 주세요</h2>
<p>
TaskFlow 회원가입이 요청되었습니다.
</p>
<p>
아래 버튼을 눌러 이메일 확인을 완료해 주세요.
</p>
<p>
<a href="{{ .ConfirmationURL }}">
이메일 확인하기
</a>
</p>
<p>
회원가입을 요청하지 않았다면 이 메일을 무시해 주세요.
</p>
제목도 서비스 언어에 맞춥니다.
예:
TaskFlow 회원가입을 확인해 주세요
버튼 문구는 기능을 명확히 드러내는 표현을 사용합니다.
이메일 확인하기
ConfirmationURL을 지우면 안 되는 이유
다음처럼 단순한 주소를 직접 넣지 않습니다.
<a href="https://taskflow.example.com">
이메일 확인하기
</a>
이 링크에는 사용자의 가입 확인에 필요한 인증 정보가 없습니다.
메일이 예쁘게 보이더라도 이메일 확인 자체가 수행되지 않습니다.
따라서 템플릿 변수는 유지하고 주변 문구만 수정합니다.
Redirect URL 설정
가입 코드에서 emailRedirectTo를 사용할 수 있습니다.
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: 'https://example.com/auth/confirmed',
},
});
실제 운영 코드에서는 example.com이 아니라 서비스 주소를 사용합니다.
그리고 Supabase Dashboard의 허용 Redirect URLs와 맞아야 합니다.
로컬 개발과 운영 환경을 분리해서 등록합니다.
예:
http://localhost:3000/auth/confirmed
https://taskflow.example.com/auth/confirmed
이 글에서는 서버 쿠키 교환용 콜백 구현까지 확장하지 않습니다.
Next.js Server Component와 동일한 인증 상태를 쿠키로 공유하는 구조는 8회차에서 @supabase/ssr을 이용해 완성합니다.
확인 완료 페이지
단순한 안내 페이지를 만들 수 있습니다.
app/auth/confirmed/page.tsx:
export default function AuthConfirmedPage() {
return (
<main>
<h1>이메일 확인 안내</h1>
<p>메일의 확인 링크를 완료했다면 로그인 화면에서 이메일과 비밀번호로 로그인해 주세요.</p>
<p>이 페이지를 열었다는 사실만으로 인증 상태를 판정하지 않습니다.</p>
</main>
);
}
이 페이지는 이메일 확인 뒤 로그인 방법을 안내하는 UI입니다. 페이지를 열었다는 사실만으로 이메일 인증 성공을 판정하지 않습니다.
이 페이지 자체가 이메일 인증을 수행하는 것은 아닙니다.
테스트는 네 단계로 나눕니다
1. 가입 요청
새 테스트 이메일로 signUp을 실행합니다.
확인할 것:
- API 오류가 없는가
- Confirm email을 사용하는 프로젝트에서 session 상태가 예상과 맞는가
2. 메일 수신
확인할 것:
- 제목이 의도한 언어인가
- 본문이 깨지지 않는가
- 링크 버튼이 존재하는가
3. 링크 클릭과 인증
확인할 것:
- 링크가 Supabase 인증 흐름을 통과하는가
- 사용자의 이메일 확인 상태가 변경되는가
4. 리디렉션
확인할 것:
- 허용된 URL로 이동하는가
- 로컬과 운영 URL이 섞이지 않았는가
메일이 발송됐다고 3번과 4번까지 성공했다고 기록하지 않습니다.
자주 발생하는 오류
무료 플랜인데 템플릿 편집 메뉴가 동작하지 않는다
2026년 무료 플랜 정책 변경을 확인합니다.
기본 Supabase 이메일 인프라를 이용하는 신규 무료 프로젝트라면 커스터마이징이 제한될 수 있습니다.
ConfirmationURL 변수를 삭제했다
메일은 열리지만 이메일 확인이 수행되지 않을 수 있습니다.
localhost는 되는데 운영에서 리디렉션이 막힌다
Site URL과 Redirect URLs의 운영 주소를 확인합니다.
이메일 수신만 보고 인증 성공이라고 판단한다
Authentication 사용자 상태와 실제 링크 클릭 결과를 별도로 확인합니다.
학습 완료 체크리스트
- Confirm email이 가입 흐름에서 어떤 역할을 하는지 설명할 수 있습니다.
- 이메일 템플릿의 확인 링크 변수를 유지해야 하는 이유를 알고 있습니다.
- emailRedirectTo와 Dashboard 허용 URL을 함께 확인할 수 있습니다.
- 메일 발송과 이메일 확인 완료를 구분할 수 있습니다.
- 2026년 무료 플랜 템플릿 제한을 알고 있습니다.
- 이 회차가 SSR 인증을 완성하는 회차가 아니라는 것을 이해했습니다.
선택 실습: 할 일 앱에 적용하기
TaskFlow는 작업을 추가·완료·삭제하는 할 일 앱입니다. 이 선택 실습에서는 데이터 저장·인증·접근 권한을 연결합니다.
이번 회차는 이메일 템플릿과 검증 기록을 추가하는 작업입니다.
선택: 연재 프로젝트와 전체 자료
연재 프로젝트를 이어 만드는 분을 위한 자료입니다. 이 글의 개념과 예제만 찾으셨다면 필요한 파일과 실행 방법만 펼쳐보세요.
TaskFlow 프로젝트 적용
이전 단계 시작 ZIP을 먼저 별도 작업 폴더에 풉니다. 현재 회차 ZIP은 적용 후 비교할 완성본입니다.
3회차 Auth 시작 ZIP에 email-template/starter와 complete 파일, 리디렉션 점검 문서를 더합니다. 메일 디자인을 바꾸어도 인증 URL 변수는 보존합니다.
이전 체크포인트
supabase-step-03-auth-basics
이번 회차의 위치
이 글은 선택 실습입니다.
건너뛰는 경우:
supabase-step-03-auth-basics
→ supabase-step-05-relational-schema
전체 순서로 따라오는 경우 이번 완료 체크포인트를 저장합니다.
현재 파일 구조
taskflow/
├─ app/
│ ├─ login/
│ │ └─ page.tsx
│ └─ auth/
│ └─ confirmed/
│ └─ page.tsx
├─ docs/
│ ├─ auth-email-template.md
│ └─ auth-redirect-checklist.md
└─ email-template/
├─ starter/
│ └─ confirmation.html
└─ complete/
└─ confirmation.html
TaskFlow 실행 코드의 핵심 구조는 3회차와 같습니다.
이번 회차는 이메일 템플릿과 검증 기록을 추가하는 작업입니다.
전체 파일·코드 보기
실습 프로젝트 전체 코드
아래는 이 회차까지의 코드를 누적한 독립 실행용 최소 Next.js App Router 학습 앱 전체입니다. 기존 Next.js 38편 앱 전체를 복제한 것이 아닙니다. ZIP에는 의존성 잠금 파일 package-lock.json도 포함되며 본문에서는 이 자동 생성 잠금 파일만 생략합니다. 기존 TaskFlow 앱을 이어서 사용한다면 기존 설정을 보존하고 해당 회차 변경 파일을 적용하세요.
.env.example
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=
.gitignore
node_modules/
.next/
.env.local
*.tsbuildinfo
app/auth/confirmed/page.tsx
export default function AuthConfirmedPage() {
return (
<main>
<h1>이메일 확인 안내</h1>
<p>메일의 확인 링크를 완료했다면 로그인 화면에서 이메일과 비밀번호로 로그인해 주세요.</p>
<p>이 페이지를 열었다는 사실만으로 인증 상태를 판정하지 않습니다.</p>
</main>
);
}
app/layout.tsx
import type { ReactNode } from "react";
export default function RootLayout({ children }: { children: ReactNode }) { return <html lang="ko"><body>{children}</body></html>; }
app/login/page.tsx
import { AuthForm } from '@/components/auth/auth-form';
export default function LoginPage() {
return (
<main>
<h1>TaskFlow 로그인</h1>
<AuthForm />
</main>
);
}
app/page.tsx
import Link from "next/link";
export default function Home() { return <main><h1>TaskFlow Supabase</h1><Link href="/tasks">작업 목록</Link> <Link href="/login">로그인</Link></main>; }
app/tasks/page.tsx
import { createPublicSupabaseClient } from '@/lib/supabase/public-server';
type PublicTask = {
id: number;
title: string;
created_at: string;
};
export const dynamic = 'force-dynamic';
export default async function TasksPage() {
const supabase = createPublicSupabaseClient();
const { data, error } = await supabase
.from('public_tasks_lab')
.select('id, title, created_at')
.order('id');
if (error) {
return (
<main>
<h1>TaskFlow</h1>
<p>Supabase 데이터를 불러오지 못했습니다.</p>
<pre>{error.message}</pre>
</main>
);
}
const tasks = (data ?? []) as PublicTask[];
return (
<main>
<h1>TaskFlow</h1>
{tasks.length === 0 ? (
<p>공개 작업이 없습니다.</p>
) : (
<ul>
{tasks.map((task) => (
<li key={task.id}>{task.title}</li>
))}
</ul>
)}
</main>
);
}
components/auth/auth-form.tsx
'use client';
import { FormEvent, useEffect, useState } from 'react';
import type { Session } from '@supabase/supabase-js';
import { createClient } from '@/lib/supabase/client';
export function AuthForm() {
const supabase = createClient();
const [session, setSession] = useState<Session | null>(null);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [message, setMessage] = useState('');
const [pending, setPending] = useState(false);
useEffect(() => {
void supabase.auth.getSession().then(({ data }) => {
setSession(data.session);
});
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, nextSession) => {
setSession(nextSession);
});
return () => subscription.unsubscribe();
}, [supabase]);
async function signUp(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
setPending(true);
setMessage('');
const { data, error } = await supabase.auth.signUp({
email,
password,
});
setPending(false);
if (error) {
setMessage(error.message);
return;
}
setMessage(
data.session
? '가입과 로그인이 완료되었습니다.'
: '가입 요청이 완료되었습니다. 이메일을 확인해 주세요.',
);
}
async function signIn() {
setPending(true);
setMessage('');
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});
setPending(false);
setMessage(error ? error.message : '로그인했습니다.');
}
async function signOut() {
setPending(true);
setMessage('');
const { error } = await supabase.auth.signOut();
setPending(false);
setMessage(error ? error.message : '로그아웃했습니다.');
}
if (session) {
return (
<section>
<p>로그인 상태입니다.</p>
<p>{session.user.email}</p>
<button type="button" onClick={signOut} disabled={pending}>
로그아웃
</button>
{message && <p>{message}</p>}
</section>
);
}
return (
<form onSubmit={signUp}>
<label>
이메일
<input
type="email"
value={email}
onChange={(event) => setEmail(event.target.value)}
required
/>
</label>
<label>
비밀번호
<input
type="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
minLength={8}
required
/>
</label>
<button type="submit" disabled={pending}>
회원가입
</button>
<button type="button" onClick={signIn} disabled={pending}>
로그인
</button>
{message && <p>{message}</p>}
</form>
);
}
docs/auth-email-template.md
# Auth Email Template
## 대상
Confirm signup
## 제목
TaskFlow 회원가입을 확인해 주세요
## 버튼
이메일 확인하기
## 반드시 유지할 값
ConfirmationURL
## 검증
- 제목 확인
- 본문 확인
- 링크 존재 확인
- 클릭 후 이메일 확인 상태 확인
- redirect 확인
## 주의
2026-06-03 이후 무료 플랜 기본 이메일 발송 인프라의 템플릿 커스터마이징 제한을 발행/적용 시점에 다시 확인한다.
docs/auth-redirect-checklist.md
# Auth Redirect Checklist
- 로컬 Redirect URL 등록
- 운영 Redirect URL 등록
- Site URL 확인
- emailRedirectTo와 허용 URL 일치
- 링크 클릭 후 최종 이동 확인
- 실제 토큰/개인정보를 문서에 기록하지 않음
docs/backend-choice.md
# TaskFlow Backend Choice
## 시작점
next-step-38-cache-update
## 선택
Supabase
## 이유
- PostgreSQL 관계형 데이터 모델을 직접 학습한다.
- Supabase Auth와 Postgres 사용자를 연결한다.
- GRANT와 RLS를 이용해 사용자별 데이터 격리를 학습한다.
- Next.js App Router에서 쿠키 기반 SSR 인증을 연결한다.
- 관계 조회와 인덱스까지 하나의 데이터 계층에서 학습한다.
## 이번 과정에서 사용하는 기능
- PostgreSQL
- Auth
- Data API
- Row Level Security
- @supabase/supabase-js
- @supabase/ssr
## 이번 과정에서 필수로 다루지 않는 기능
- Realtime
- Storage
- Edge Functions
필요 기능이 아니므로 과정 범위를 무리하게 늘리지 않는다.
## 보안 원칙
- 브라우저에는 publishable key만 사용한다.
- secret key는 클라이언트에 포함하지 않는다.
- 사용자 데이터는 RLS를 활성화한다.
- 화면에서 숨기는 것으로 권한 검증을 대신하지 않는다.
- 실제 인증 토큰과 개인정보를 학습 자료에 저장하지 않는다.
## 백엔드 트랙
Firebase 완료본을 이어서 수정하지 않는다.
Next.js TaskFlow 최종 체크포인트에서 별도 Supabase 브랜치를 시작한다.
## 다음 단계
Supabase 프로젝트를 만들고 공개 학습 데이터를 한 번 조회한다.
email-template/complete/confirmation.html
<h2>TaskFlow 회원가입을 확인해 주세요</h2>
<p>
TaskFlow 회원가입이 요청되었습니다.
</p>
<p>
아래 버튼을 눌러 이메일 확인을 완료해 주세요.
</p>
<p>
<a href="{{ .ConfirmationURL }}">
이메일 확인하기
</a>
</p>
<p>
회원가입을 요청하지 않았다면 이 메일을 무시해 주세요.
</p>
email-template/starter/confirmation.html
<h2>Confirm your signup</h2>
<p>Follow this link to confirm your user:</p>
<p>
<a href="{{ .ConfirmationURL }}">
Confirm your mail
</a>
</p>
lib/supabase/client.ts
'use client';
import { createClient as createSupabaseClient, type SupabaseClient } from '@supabase/supabase-js';
let client: SupabaseClient | undefined;
export function createClient() {
if (client) {
return client;
}
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
const key = process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY;
if (!url || !key) {
throw new Error('Supabase 공개 설정이 필요합니다.');
}
client = createSupabaseClient(url, key);
return client;
}
lib/supabase/public-server.ts
import 'server-only';
import { createClient } from '@supabase/supabase-js';
export function createPublicSupabaseClient() {
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
const key = process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY;
if (!url || !key) {
throw new Error('Supabase 공개 설정이 필요합니다.');
}
return createClient(url, key, {
auth: {
persistSession: false,
autoRefreshToken: false,
detectSessionInUrl: false,
},
});
}
next-env.d.ts
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
import "./.next/types/root-params.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
package.json
{
"name": "taskflow-supabase-checkpoint",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"next": "16.3.5",
"react": "19.3.0",
"react-dom": "19.3.0",
"@supabase/supabase-js": "2.116.0",
"server-only": "0.0.1"
},
"devDependencies": {
"typescript": "5.9.3",
"@types/node": "24.10.1",
"@types/react": "19.2.7",
"@types/react-dom": "19.2.3"
}
}
README.md
# Supabase 04 완료 체크포인트 — email-confirmation
원문 회차 코드를 누적한 독립 실행용 최소 Next.js App Router 학습 앱입니다. 이전 Next.js 38편 앱 전체나 추가 CRUD 화면을 복제한 프로젝트가 아닙니다.
Node.js 24.18.0, npm 11.16.0 기준입니다.
1. `npm ci`
2. `.env.example`을 `.env.local`로 복사하고 개발 전용 Supabase URL/publishable key를 입력합니다.
3. 아래 순서대로 개발 DB에 해당 SQL을 한 번씩 적용합니다. 같은 객체가 있으면 덮어쓰지 않습니다.
4. `npm run dev` 후 `/tasks`를 확인합니다.
5. `npm run typecheck` 및 `npm run build`를 실행합니다.
SQL: `supabase/learning/02_public_tasks_lab.sql`.
`/login`은 이메일/비밀번호 인증 화면입니다. 실제 가입/확인 메일은 본인 개발 프로젝트에서 직접 검증하세요.
실제 secret/service_role 키, 비밀번호, 원격 DB 계정은 포함하지 않습니다. node_modules/.next/.env.local은 제외했습니다. ZIP의 package-lock.json으로 정확한 의존성을 재현하세요.
supabase/learning/02_public_tasks_lab.sql
begin;
create table public.public_tasks_lab (
id bigint generated always as identity primary key,
title text not null,
created_at timestamptz not null default now()
);
insert into public.public_tasks_lab (title)
values
('Supabase 연결 확인'),
('RLS 공개 읽기 확인');
alter table public.public_tasks_lab
enable row level security;
revoke all
on table public.public_tasks_lab
from public, anon, authenticated;
grant select
on table public.public_tasks_lab
to anon, authenticated;
create policy "public_tasks_lab_read"
on public.public_tasks_lab
for select
to anon, authenticated
using (true);
commit;
tsconfig.json
{
"compilerOptions": {
"target": "ES2017",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": [
"./*"
]
},
"allowJs": true
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}
검증 범위
실제 이메일 발송은 프로젝트의 플랜과 SMTP 설정이 필요합니다.
본문에서는 설정 위치와 검증 절차를 제공하고, 실제 메일 수신·링크 클릭을 수행하지 않은 상태라면 “실행 검증 완료”라고 기록하지 않습니다.
완료 체크포인트
supabase-step-04-email-confirmation
프로젝트 적용 후 확인 기록
독자 질문: 한국어 확인 메일을 받은 것만으로 계정 확인이 끝났다고 할 수 있을까?
따라 할 단계
- 테스트 프로젝트의 Confirm email과 Redirect URL을 확인하고 템플릿 제목·본문·버튼 문구를 수정한다.
- A 이메일로 가입해 메일 수신, 확인 링크 클릭, 인증 완료 페이지 리디렉션을 각각 기록한다.
- 별도 B 이메일로도 같은 절차를 반복하고, 확인 전 로그인 제한 여부를 설정에 맞게 비교한다.
예상 결과와 A/B 계정 범위
- 메일 발송, 링크 확인, 인증 상태, 리디렉션은 서로 다른 결과다. A와 B 모두 각자 받은 링크로 확인해야 한다.
- 두 계정의 메일 확인은 Auth 동작의 검증이며, tasks 테이블의 A/B 데이터 격리를 증명하지 않는다.
흔한 오류 점검
- 버튼 링크 변수나 허용 Redirect URL을 누락했으면 템플릿과 URL 설정을 다시 확인한다.
- 대시보드 편집이 제한되면 플랜 제한 여부를 공식 안내에서 확인하고 앱 코드 오류와 구분한다.
검증 기록: 이번 편집에서 실제 Supabase 요청·A/B 계정 실험은 실행하지 않았으므로 미확인입니다.
다음 학습
5회차에서 확인된 계정을 public.profiles와 public.tasks에 연결한다.
다음 학습
다음 글에서는 Auth 사용자를 실제 TaskFlow 데이터와 연결합니다.
public.profiles와 public.tasks를 만들고, PK·FK·CHECK·RLS를 이용해 관계형 데이터 모델의 기반을 준비합니다.
다음 글:
Supabase Postgres 관계형 데이터 모델링: 외래키와 TaskFlow 스키마 설계
공식 문서
확인일: 2026-09-21
- Password Auth: https://supabase.com/docs/guides/auth/passwords
- Redirect URLs: https://supabase.com/docs/guides/auth/redirect-urls
- Email Template Changelog: https://supabase.com/changelog/46599-changes-to-email-template-customisation-on-free-tier
체크포인트 다운로드
글 소개·학습 준비·전체 목차

Supabase · 4 / 9 · 선택 실습 · 가입 확인 메일
가입 확인 메일은 안내 문구와 실제 인증 링크를 함께 점검해야 합니다. 상단은 HTML 파일만으로 연습하고, 하단은 Auth 앱에 적용해 실제 발송 흐름을 확인합니다.
이번 글은 기능 구현의 필수 회차가 아니라, 한국어 서비스에서 사용자가 실제로 받는 가입 확인 메일을 점검하는 선택 실습입니다.
메일 제목과 본문을 바꾸는 것보다 더 중요한 것은 확인 링크 변수를 유지하고, 발송 → 링크 클릭 → 이메일 확인 → 리디렉션이 각각 정상인지 분리해서 검사하는 것입니다.
이 회차를 건너뛰어도 5회차 관계형 데이터 모델링으로 바로 진행할 수 있습니다.
이번 글에서 배우는 내용
- Supabase Email Provider와 Confirm email
- Confirm signup 메일 템플릿
- 제목·본문·버튼 문구 변경
- 확인 링크 변수 유지
- Site URL과 Redirect URLs
- emailRedirectTo의 역할
- 가입 요청 성공과 이메일 인증 완료 구분
- 무료 플랜 기본 이메일 서비스의 템플릿 제한
- custom SMTP를 사용하는 이유
선수 지식
- 3회차 Supabase Auth 기본
- HTML 링크 구조
- 환경 변수와 URL 기본 개념
목차
이 글이 도움이 되었나요?
읽음 기록
읽음 — / 전체 9개 · 필수 8개
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.