뉴스레터 가입 폼4분 분량
내 뉴스레터 폼의 가입을 MailerLite로 보내기
내 가입 폼을 MailerLite에 연결합니다. 릴레이가 새 독자를 더블 옵트인을 위해 미확인으로 추가하고, 구독자에게 두 번 확인을 요청하지 않습니다.
내 폼을 Ikiform으로 POST하고, MailerLite API를 호출하는 릴레이로 웹훅을 보내세요. 릴레이는 먼저 주소를 조회합니다. 새 독자는 unconfirmed로 만들어지며, API 설정이 켜져 있으면 MailerLite가 더블 옵트인 이메일을 보냅니다. 기존 구독자는 새 그룹에만 추가됩니다. 이 조회 덕분에 확인된 구독자가 다시 확인을 요청받지 않습니다.
MailerLite에서 다른 점
MailerLite의 구독자 생성 엔드포인트는 upsert입니다. /api/subscribers로 POST 한 번이면 새 구독자를 만들거나 기존 구독자를 업데이트하고, 201이나 200으로 응답합니다. 비파괴적이라서 빼먹은 그룹과 필드는 그대로 남습니다. 문제는 status입니다. 계정 설정 Double opt-in for API and integrations(API 및 연동용 더블 옵트인)가 켜져 있으면 MailerLite는 새 API 구독자에게 확인 이메일을 보냅니다. 기존의 활성 또는 수신 거부 구독자는 호출이 status: "unconfirmed"를 보낼 때만 확인 이메일을 받습니다.
MailerLite 도움말에 따르면 기존의 활성 또는 수신 거부 구독자는 호출에 "status": "unconfirmed"가 없으면 확인 이메일을 받지 않습니다. 그래서 항상 unconfirmed를 보내는 릴레이는 충성 독자의 반복 가입을 모두 새 확인 요청으로 바꿉니다. 해결책은 무엇을 보낼지 정하기 전에 주소를 조회하는 것입니다.
- 릴레이가 Ikiform의 웹훅을 검증합니다.
- MailerLite에 이메일로 구독자를 요청합니다.
- 없음: 상태 unconfirmed와 위치의 그룹으로 만들고, MailerLite가 확인 이메일을 보냅니다.
- 있고 active나 unconfirmed: 상태 없이 그룹만 추가하므로 확인 상태는 바뀌지 않습니다.
- 있고 unsubscribed: 아무것도 하지 않으므로 가입이 수신 거부를 몰래 되돌리지 않습니다.
MailerLite 설정하기
- MailerLite에서 구독 설정을 열고 Double opt-in for API and integrations를 켜세요. 확인 이메일은 거기서 편집합니다. MailerLite는 API로 추가된 모든 구독자에게 같은 이메일을 보냅니다.
- "Website footer"와 "Blog posts"처럼 위치마다 그룹을 만들고 각 그룹의 ID를 기록하세요.
- Integrations에서 API 토큰을 만들고 릴레이의 환경 변수용으로 보관하세요.
릴레이
이것은 Express 서버입니다. 서명은 Ikiform이 보낸 정확한 바이트를 대상으로 하고 JSON을 먼저 파싱하면 바이트가 바뀌므로, 원시 본문을 텍스트로 읽습니다.
import express from "express";
import { verifyWebhook, WebhookVerificationError } from "@ikiform/sdk/webhooks";
const API = "https://connect.mailerlite.com/api";
const GROUPS: Record<string, string> = {
footer: process.env.MAILERLITE_GROUP_FOOTER!,
post: process.env.MAILERLITE_GROUP_POST!,
};
function mailerlite(path: string, init: RequestInit = {}) {
return fetch(`${API}${path}`, {
...init,
headers: {
Authorization: `Bearer ${process.env.MAILERLITE_API_KEY}`,
"Content-Type": "application/json",
Accept: "application/json",
},
});
}
async function subscribe(email: string, name: string | undefined, source: string) {
const found = await mailerlite(`/subscribers/${encodeURIComponent(email)}`);
if (found.status !== 200 && found.status !== 404) return found;
const groups = [GROUPS[source] ?? process.env.MAILERLITE_GROUP_FOOTER!];
const fields = name ? { name } : {};
if (found.status === 404) {
return mailerlite("/subscribers", {
method: "POST",
body: JSON.stringify({ email, fields, groups, status: "unconfirmed" }),
});
}
const existing = await found.json();
if (existing.data.status === "unsubscribed") return new Response(null, { status: 208 });
return mailerlite("/subscribers", { method: "POST", body: JSON.stringify({ email, fields, groups }) });
}
const app = express();
app.post("/hooks/mailerlite", express.text({ type: "application/json", limit: "512kb" }), async (req, res) => {
let event;
try {
event = await verifyWebhook({ payload: req.body, headers: req.headers, secret: process.env.IKIFORM_WEBHOOK_SECRET! });
} catch (error) {
if (error instanceof WebhookVerificationError) return res.status(400).send(error.reason);
return res.status(500).end();
}
if (event.type !== "submission.created") return res.status(204).end();
const { email, first_name, consent, source } = event.data.fields;
if (consent !== true || typeof email !== "string") return res.status(200).send("Skipped");
const answer = await subscribe(
email,
typeof first_name === "string" ? first_name : undefined,
typeof source === "string" ? source : "footer",
);
if (answer.status === 208) return res.status(200).send("Unsubscribed earlier, left alone");
if (answer.status === 429) {
return res.status(503).set("retry-after", answer.headers.get("retry-after") ?? "60").send("MailerLite rate limit");
}
if (!answer.ok) return res.status(502).send(`MailerLite answered ${answer.status}: ${await answer.text()}`);
res.status(204).end();
});
app.listen(Number(process.env.PORT ?? 3000));- MailerLite는 토큰을
Authorization: Bearer로 받습니다. 기본 URL은https://connect.mailerlite.com/api입니다. GET /api/subscribers/{email}은 이메일이나 ID를 받고, 구독자가 없으면404로 응답합니다.groups는 이미 존재하는 그룹 ID를 받습니다. 틀린 ID는422로 거부되며, 릴레이는 이를 MailerLite의 메시지와 함께502로 전달합니다.fields.name은 MailerLite의 기본 이름 필드입니다. 커스텀 필드는 보내기 전에 MailerLite에 있어야 합니다.208은 수신 거부한 독자를 위한 내부 표시입니다. Ikiform에는200과 전달 로그의 메모로 보입니다.
443 포트의 공개 HTTPS URL을 주는 곳이라면 어디든 배포하고, Ikiform의 Webhooks에 그 URL을 추가하고, whsec_ 시크릿을 IKIFORM_WEBHOOK_SECRET에 넣으세요. 재시도와 페이로드는 웹훅 문서에서 다루고, 자체 서버에는 데이터베이스를 쓰는 같은 Express 구성이 있습니다.
동의 기록이 있는 곳
MailerLite의 생성 호출은 subscribed_at, ip_address, opted_in_at, optin_ip를 받습니다. IP 필드는 빼세요. Ikiform의 웹훅에는 방문자의 IP 주소나 그 해시가 절대 포함되지 않으므로 릴레이가 보낼 실제 값이 없고, 지어낸 값은 없는 것보다 나쁩니다.
가입 기록은 Ikiform에 남습니다. 제출에 타임스탬프, origin, referrer, 국가와 함께 consent: true가 보관됩니다. 확인 기록은 독자가 링크를 클릭한 시점을 기록하는 MailerLite에 있습니다. 둘을 합치면 누군가 언제 체크박스를 선택했고 언제 주소가 자기 것임을 증명했는지 보여 줄 수 있습니다. 필요할 때 Submissions에서 Ikiform 쪽을 CSV로 내보내세요.
레이트 리밋
MailerLite의 전역 API 한도는 분당 120개 요청입니다. 릴레이는 가입마다 두 번 호출하므로 분당 약 60건이 들어갑니다. 그 이상이면 MailerLite가 429로, 릴레이가 Retry-After와 함께 503으로 응답하고, Ikiform이 나중에 같은 전달로 다시 시도합니다.
| MailerLite 응답 | 릴레이 응답 | Ikiform |
|---|---|---|
201 또는 200 | 204 | 전달 성공 |
429 | Retry-After와 함께 503 | 기다린 뒤 재시도 |
401 | 502 | 재시도. 토큰을 고친 뒤 실패한 전달에서 Retry now를 쓰세요 |
422 | 오류와 함께 502 | 재시도. 대개 그룹 ID나 필드 이름이 틀린 경우입니다 |
주의할 점
status: "active"를 보내지 마세요. 그 사람을 구독 상태로 표시하며, 그래도 확인 이메일이 나가는지는 MailerLite 문서에 나와 있지 않습니다. 보내는 상태로 테스트하세요.- 재구독은 선택입니다. MailerLite에는 수신 거부한 사람을 위한
resubscribe플래그가 있습니다. 폼이 그렇다고 명시하고 독자가 다시 동의한다는 것을 알 때만 보내세요. - 토글이 중요합니다. MailerLite는 Double opt-in for API and integrations가 켜져 있을 때만 API 가입에 확인 이메일을 보냅니다.
- 확인 이메일은 하나. API 더블 옵트인 이메일은 모든 API 가입이 공유합니다. 위치마다 문구가 달라야 한다면 대신 별도의 MailerLite 폼을 쓰세요.
- Ikiform의 중복. Ikiform은 받아들인 모든 POST를 보관하고, MailerLite는 이메일당 구독자 하나를 보관합니다. 구독자 수는 Ikiform 대시보드가 아니라 MailerLite에서 세세요.
작동 확인하기
- Ikiform의 엔드포인트에서 테스트를 보내세요. 전달에
204가 표시됩니다. - 내 새 주소로 구독하세요. MailerLite에 푸터 그룹의 미확인 구독자로 표시되고 확인 이메일이 도착합니다.
- 확인한 뒤 블로그 글에서 다시 구독하세요. 구독자는 활성으로 유지되고, 두 번째 그룹에 들어가며, 새 확인 이메일은 받지 않습니다.
- MailerLite에서 그 주소를 수신 거부하고 한 번 더 구독하세요. 전달 로그에 수신 거부 메모가 표시되고, MailerLite에서는 여전히 수신 거부 상태입니다.
질문
네. 폼을 Ikiform으로 POST하고, 릴레이가 MailerLite API로 각 가입을 알맞은 그룹에 추가하게 하세요. 폼은 내 마크업으로 남고 뉴스레터는 여전히 MailerLite가 보냅니다.
MailerLite 구독 설정에서 Double opt-in for API and integrations가 꺼져 있거나, 릴레이가 구독자를 active로 만들었습니다. 설정을 켜 두고, status unconfirmed를 보내고, 내 주소로 테스트하세요.
이 릴레이에서는 아닙니다. 먼저 주소를 조회하고 새 구독자일 때만 unconfirmed를 보냅니다. 기존 구독자는 새 그룹에 추가될 뿐 다른 것은 바뀌지 않습니다.
위치마다 폼의 hidden source 입력에 다른 값을 주고, 릴레이에서 각 값을 MailerLite 그룹 ID에 매핑하세요. 두 번째 위치에서 가입한 기존 구독자는 그 그룹에도 들어가고 첫 그룹도 유지합니다.
아니요. Ikiform은 폼을 제출한 사람에게 절대 이메일을 보내지 않습니다. 확인 이메일, 모든 뉴스레터, 수신 거부 링크는 MailerLite가 보냅니다.
막히는 단계가 있거나 내 상황에 맞는 가이드가 필요하다면 이메일을 보내세요: hello@ikiform.com.