# 직접 만든 뉴스레터 폼의 더블 옵트인

> Ikiform은 확인 이메일을 보내지 않으므로 더블 옵트인은 이메일 도구에서 실행됩니다. 릴레이가 각 가입을 미확인으로 추가하고 도구가 링크를 보냅니다.

2026-10-25 게시 · https://ikiform.com/ko/guides/newsletter-signup/double-opt-in

Ikiform은 가입한 사람에게 절대 이메일을 보내지 않으므로, 확인 이메일은 뉴스레터를 보내는 도구에서 나가야 합니다. 릴레이가 Ikiform의 서명된 웹훅을 받아 그 도구에 독자를 미확인으로 추가하면, 도구가 링크를 이메일로 보내고, 클릭해야만 구독자가 됩니다. 도구마다 켜는 방식이 다르며, 릴레이는 이미 구독 중인 사람에게 두 번째 확인을 보내면 안 됩니다.

## 이 경우가 다른 점

Ikiform은 폼을 작성한 사람에게 이메일을 보내지 않습니다. 알림 이메일은 나와 팀에게만 가므로 자동 응답, 확인 이메일, 더블 옵트인이 없습니다. 그런 이메일은 [웹훅](https://ikiform.com/ko/docs/webhooks.md)으로 트리거해 이메일 도구에서 보내세요. 서비스의 임베드를 쓰면 확인은 그 서비스 폼의 설정입니다. 내 폼을 쓰면 두 시스템이 관여하고, 확인은 목록을 관리하는 쪽에 속합니다. 할 일은 각 가입이 확인을 트리거하는 상태로 그 도구에 도달하게 하고, 그러면 안 될 때는 트리거하지 않게 하는 것입니다.

더블 옵트인은 이메일 도구 안의 상태 머신입니다. 독자는 미확인으로 시작하고, 호를 받을 수 없으며, 클릭해야만 구독자가 됩니다. Ikiform의 역할은 릴레이가 `204`로 응답하면 끝납니다.

다이어그램: 더블 옵트인 가입의 흐름, 폼부터 확인된 구독자까지

1. 독자가 동의 체크박스를 선택하고 폼을 제출하면 Ikiform으로 POST됩니다.
2. Ikiform이 가입을 저장하고 릴레이로 서명된 웹훅을 보냅니다.
3. 릴레이가 서명을 검증하고 독자를 이메일 도구에 미확인으로 추가합니다.
4. 이메일 도구가 독자에게 확인 링크를 이메일로 보냅니다.
5. 클릭하면 도구가 확인됨으로 표시하고 모든 호를 보냅니다.
6. 클릭하지 않으면 미확인 상태로 남고 뉴스레터를 받지 않습니다.

확인 이메일의 문구, 링크가 여는 페이지, 리마인더를 포함해 릴레이 이후의 모든 일은 이메일 도구에서 일어납니다.

## 도구별 설정

도구마다 미확인의 개념이 다르고, 새 API 구독자를 미확인으로 만드는 방법도 다릅니다. 잘못하면 구독자가 이메일 없이 이미 확인된 상태로 추가됩니다.

| 도구 | API 가입이 확인 이메일을 받는 방법 | 함정 |
| --- | --- | --- |
| [Kit](https://ikiform.com/ko/guides/newsletter-signup/kit.md) | `state: "inactive"`로 구독자를 만든 뒤, 인센티브 이메일이 켜진 폼에 추가하세요. Kit의 대량 폼 문서에 따르면 이렇게 하면 인센티브 이메일이 발송됩니다. 단일 엔드포인트는 내 주소로 테스트하세요 | 구독자 생성의 기본값은 `state: "active"`이며, 그 엔드포인트로는 나중에 상태를 바꿀 수 없습니다 |
| [MailerLite](https://ikiform.com/ko/guides/newsletter-signup/mailerlite.md) | 구독 설정에서 **Double opt-in for API and integrations**(API 및 연동용 더블 옵트인)를 켜세요. `status: "unconfirmed"`도 함께 보내세요. MailerLite는 기존 구독자에게 이메일을 보내기 전에 이 값이 필요합니다 | 이미 활성인 주소에 `unconfirmed`를 보내면 다시 확인을 요청합니다 |
| [beehiiv](https://ikiform.com/ko/guides/newsletter-signup/beehiiv.md) | 구독 생성 시 `double_opt_override: "on"`을 보내거나, 퍼블리케이션 자체 설정을 쓰려면 `"not_set"`을 보내세요 | `"off"`는 이메일 없이 구독자를 바로 활성화합니다 |

의존하기 전에 각각 내 주소로 테스트하세요. 도구 설정은 바뀌고, 이메일 자체의 제목, 문구, 링크가 여는 페이지는 도구에서 설정합니다.

## 한 번만 확인을 요청하는 릴레이

단순한 릴레이는 웹훅이 도착할 때마다 확인을 요청합니다. 두 경우에 이는 잘못입니다. 1분에 세 번 제출한 독자는 이메일을 세 통 받고, 피해자의 주소를 가입시키는 봇은 시도할 때마다 그 사람의 받은편지함에 내 확인 이메일을 넣습니다. 이 Cloudflare Worker는 주소당 하루에 최대 한 번만 도구에 확인을 요청합니다.

`src/index.ts`:

```ts
import { verifyWebhook, WebhookVerificationError } from "@ikiform/sdk/webhooks";
import { addUnconfirmed } from "./tool";

export interface Env {
  IKIFORM_WEBHOOK_SECRET: string;
  TOOL_API_KEY: string;
  SEEN: KVNamespace;
}

const DAY = 86_400;

async function digest(text: string) {
  const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
  return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
}

export default {
  async fetch(request: Request, env: Env) {
    if (request.method !== "POST") return new Response(null, { status: 405 });
    const payload = await request.text();
    let event;
    try {
      event = await verifyWebhook({ payload, headers: request.headers, secret: env.IKIFORM_WEBHOOK_SECRET });
    } catch (error) {
      if (error instanceof WebhookVerificationError) return new Response(error.reason, { status: 400 });
      throw error;
    }
    if (event.type !== "submission.created" || event.data.fields.consent !== true) {
      return new Response(null, { status: 204 });
    }

    const email = String(event.data.fields.email).trim().toLowerCase();
    const key = `confirm:${await digest(email)}`;
    if (await env.SEEN.get(key)) return new Response("Confirmation already requested today", { status: 200 });

    const answer = await addUnconfirmed(env, email, event.data.fields.first_name);
    if (answer.status === 429) {
      return new Response("Rate limited", { status: 503, headers: { "retry-after": answer.headers.get("retry-after") ?? "60" } });
    }
    if (!answer.ok) return new Response(`Email tool answered ${answer.status}`, { status: 502 });

    await env.SEEN.put(key, event.id, { expirationTtl: DAY });
    return new Response(null, { status: 204 });
  },
};
```

`wrangler.jsonc`:

```json
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "newsletter-relay",
  "main": "src/index.ts",
  "compatibility_date": "2026-10-01",
  "routes": [{ "pattern": "hooks.example.com", "custom_domain": true }],
  "kv_namespaces": [{ "binding": "SEEN", "id": "<your KV namespace id>" }]
}
```

- `./tool`의 `addUnconfirmed`는 위 표의 도구 호출입니다. 본문은 Kit, MailerLite, beehiiv 페이지에서 복사하세요.
- KV 키는 소문자로 바꾼 주소의 SHA-256이므로 저장소에 주소 자체는 남지 않습니다. 하루 뒤 만료됩니다.
- 키는 도구가 요청을 받아들인 뒤에만 기록됩니다. 도구가 실패하면 Ikiform이 재시도하고, 재시도는 막히지 않습니다.
- 반복에 `200`으로 응답하면 Ikiform에 전달이 성공했다고 알려 재시도하지 않게 하고, 본문이 전달 로그에 이유를 설명합니다.
- Worker는 커스텀 도메인에서 실행됩니다. Ikiform은 `workers.dev` 호스트 이름을 웹훅 URL로 거부합니다. [웹훅 문서](https://ikiform.com/ko/docs/webhooks.md)에 모든 URL 규칙과 각 응답의 의미가 있고, [자체 서버](https://ikiform.com/blog/form-submissions-to-your-own-server.md)에서 각 전달을 한 번만 저장하는 릴레이를 보여 줍니다.

> KV는 위치 간에 최종적 일관성을 가지므로, 몇 초 안에 같은 주소로 온 두 전달이 모두 통과할 수 있습니다. 확인 이메일이라면 받아들일 만합니다. 엄격한 보장이 필요하다면 키를 Durable Object나 고유 열이 있는 데이터베이스에 두세요.

## 폼이 안내해야 할 내용

"구독해 주셔서 감사합니다"를 본 독자가 뉴스레터 폴더에서 아무것도 찾지 못하면 실패했다고 생각합니다. 도착한 페이지에서 다음에 무슨 일이 일어나는지 알려 주세요.

1. 성공 문구나 감사 페이지에 열어야 할 이메일이 있다고 적으세요. "거의 다 됐습니다. 보내 드린 이메일을 열어 주소를 확인해 주세요."
2. 찾아야 할 발신자 이름을 알려 주고, 몇 분 안에 아무것도 오지 않으면 스팸함을 확인하라고 안내하세요.
3. 이메일 도구에서 확인 링크가 여는 페이지를 내 "구독 완료" 페이지로 설정하면 여정이 내 사이트에서 끝납니다.

폼의 동의 체크박스와 확인 이메일은 하는 일이 다릅니다. 체크박스는 누군가 내 페이지에서 동의했다는 것을 기록합니다. 클릭은 그 주소가 동의한 사람의 것임을 증명합니다. 대부분의 목록은 둘 다 원합니다. [뉴스레터 폼 템플릿](https://ikiform.com/forms/newsletter-signup)에 필수 체크박스가 있습니다.

## 주의할 점

- **기존 구독자.** 이미 확인된 사람이 새 페이지에서 다시 가입합니다. 미확인으로 되돌리지 마세요. [MailerLite 페이지](https://ikiform.com/ko/guides/newsletter-signup/mailerlite.md)에서 주소를 먼저 조회하는 릴레이를 보여 줍니다.
- **수신 거부한 사람.** 가입이 항상 수신 거부를 되돌려도 된다는 허락은 아닙니다. beehiiv는 `reactivate_existing: true`를 보낼 때만 다시 활성화하고, MailerLite는 `resubscribe: true`일 때만 재구독합니다. 폼이 그렇다고 명시할 때만 보내세요.
- **링크 스캐너.** 일부 회사 메일 필터는 확인을 위해 들어오는 메일의 링크를 엽니다. 도구가 단순히 페이지가 로드되면 확인한다면, 스캐너가 아무도 클릭하지 않은 주소를 확인할 수 있습니다. 도구의 확인 페이지가 버튼을 누르게 하는지 확인하세요.
- **전달률.** 확인 이메일은 독자가 나에게서 받는 첫 이메일입니다. 도구에서 인증된 도메인으로 보내세요. 그러지 않으면 스팸함에 들어가 가입이 완료되지 않습니다.
- **미확인 인원이 쌓입니다.** 아직 구독자가 아닙니다. 도구에서 리마인더를 보낼지 정하고, 그다음에는 그대로 두세요.

## 작동 확인하기

1. 내 주소로 폼을 제출하세요. 페이지가 받은편지함을 확인하라고 알려 줍니다.
2. Ikiform의 **Webhooks**에서 전달을 여세요. 릴레이의 `204`와 함께 `succeeded`가 표시됩니다.
3. 이메일 도구에서 구독자를 여세요. 도구에 따라 unconfirmed, inactive, pending 상태입니다.
4. 확인 이메일을 열고 클릭하세요. 구독자가 active나 confirmed가 됩니다.
5. 하루 안에 같은 주소로 다시 제출하세요. 전달에 "Confirmation already requested today"와 함께 `200`이 표시되고 두 번째 이메일은 오지 않습니다.
6. 이미 확인된 주소로 가입하세요. 확인 상태가 유지되고 새 확인 이메일은 오지 않습니다.

## 질문

### Ikiform이 더블 옵트인 확인 이메일을 보낼 수 있나요?

아니요. Ikiform은 폼을 제출한 사람에게 절대 이메일을 보내지 않습니다. 확인 이메일은 릴레이나 자동화 도구를 거친 Ikiform의 웹훅으로 트리거되어 이메일 도구가 보냅니다.

### 더블 옵트인을 쓰면 동의 체크박스가 여전히 필요한가요?

하는 일이 다릅니다. 체크박스는 그 사람이 내 페이지에서 동의했다는 것을 타임스탬프와 함께 기록합니다. 확인 클릭은 그 주소의 소유자임을 증명합니다. 대부분의 목록은 둘 다 유지합니다.

### 구독자가 확인 이메일을 받지 못한 이유는 무엇인가요?

릴레이가 이미 확인된 상태로 만들었을 가능성이 큽니다. Kit의 구독자 생성 기본값은 active이고, MailerLite는 API 더블 옵트인 설정과 기존 구독자에 대한 unconfirmed 상태가 필요하며, beehiiv의 double_opt_override를 off로 하면 이메일을 건너뜁니다. 도구에서 구독자의 상태를 확인하세요.

### 릴레이 대신 Zapier나 Make를 써도 되나요?

네. Ikiform 웹훅을 자동화로 보내고 이메일 도구용 액션을 쓰세요. 액션의 옵션이 구독자를 미확인 상태로 만드는지 확인하세요. 필터 단계를 추가하지 않으면 하루 한 번 제한은 사라집니다.

## 가이드 더 보기

- [뉴스레터 가입 폼](https://ikiform.com/ko/guides/newsletter-signup.md): 직접 작성한 마크업으로 뉴스레터 가입 폼을 만들고, 동의를 기록하고, 봇 가입을 막고, 각 구독자를 Kit, MailerLite, beehiiv 등 어떤 도구로든 넘깁니다.
- [Kit (구 ConvertKit)](https://ikiform.com/ko/guides/newsletter-signup/kit.md): 구독자를 만들고, 인센티브 이메일을 위해 Kit 폼에 추가하고, 가입 위치를 태그하는 릴레이로 내 뉴스레터 폼을 Kit에 연결합니다.
- [MailerLite](https://ikiform.com/ko/guides/newsletter-signup/mailerlite.md): 내 가입 폼을 MailerLite에 연결합니다. 릴레이가 새 독자를 더블 옵트인을 위해 미확인으로 추가하고, 구독자에게 두 번 확인을 요청하지 않습니다.
- [beehiiv](https://ikiform.com/ko/guides/newsletter-signup/beehiiv.md): 내 가입 폼을 beehiiv에 연결합니다. 릴레이가 더블 옵트인을 켜고, 위치를 UTM source로, 페이지를 referrer로 해서 각 구독을 만듭니다.
- [정적 사이트](https://ikiform.com/ko/guides/newsletter-signup/static-site.md): Hugo, Jekyll, Eleventy, Astro 정적 사이트에 뉴스레터 가입 폼을 추가하고, 이메일 도구의 API 키는 페이지에서 빼고, 확인은 도구에서 합니다.
- [Next.js 뉴스레터 폼](https://ikiform.com/ko/guides/newsletter-signup/nextjs.md): Next.js 푸터 뉴스레터 폼입니다. 동의 확인과 Turnstile이 있는 서버 액션이 Ikiform으로 POST하고, 라우트 핸들러가 독자를 Kit에 추가합니다.
