뉴스레터 가입 폼

뉴스레터 가입 폼4분 분량

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

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

Preet SutharIkiform 창업자

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

이 경우가 다른 점

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

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

더블 옵트인 가입의 흐름, 폼부터 확인된 구독자까지폼동의 체크Ikiform저장, 웹훅 서명릴레이미확인으로 추가이메일 도구링크 발송확인됨모든 호 수신클릭 없음미확인 유지, 호 없음전송웹훅API클릭더블 옵트인 가입의 흐름, 폼부터 확인된 구독자까지폼동의 체크Ikiform저장, 웹훅 서명릴레이미확인으로 추가이메일 도구링크 발송확인됨모든 호 수신클릭 없음미확인 유지, 호 없음전송웹훅API클릭
  1. 독자가 동의 체크박스를 선택하고 폼을 제출하면 Ikiform으로 POST됩니다.
  2. Ikiform이 가입을 저장하고 릴레이로 서명된 웹훅을 보냅니다.
  3. 릴레이가 서명을 검증하고 독자를 이메일 도구에 미확인으로 추가합니다.
  4. 이메일 도구가 독자에게 확인 링크를 이메일로 보냅니다.
  5. 클릭하면 도구가 확인됨으로 표시하고 모든 호를 보냅니다.
  6. 클릭하지 않으면 미확인 상태로 남고 뉴스레터를 받지 않습니다.
확인 이메일의 문구, 링크가 여는 페이지, 리마인더를 포함해 릴레이 이후의 모든 일은 이메일 도구에서 일어납니다.

도구별 설정

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

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

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

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

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

src/index.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
{
  "$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로 거부합니다. 웹훅 문서에 모든 URL 규칙과 각 응답의 의미가 있고, 자체 서버에서 각 전달을 한 번만 저장하는 릴레이를 보여 줍니다.
KV는 위치 간에 최종적 일관성을 가지므로, 몇 초 안에 같은 주소로 온 두 전달이 모두 통과할 수 있습니다. 확인 이메일이라면 받아들일 만합니다. 엄격한 보장이 필요하다면 키를 Durable Object나 고유 열이 있는 데이터베이스에 두세요.

폼이 안내해야 할 내용

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

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

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

주의할 점

  • 기존 구독자. 이미 확인된 사람이 새 페이지에서 다시 가입합니다. 미확인으로 되돌리지 마세요. MailerLite 페이지에서 주소를 먼저 조회하는 릴레이를 보여 줍니다.
  • 수신 거부한 사람. 가입이 항상 수신 거부를 되돌려도 된다는 허락은 아닙니다. 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. 이미 확인된 주소로 가입하세요. 확인 상태가 유지되고 새 확인 이메일은 오지 않습니다.

질문

막히는 단계가 있거나 내 상황에 맞는 가이드가 필요하다면 이메일을 보내세요: hello@ikiform.com.