ニュースレター登録フォーム

ニュースレター登録フォーム2 分で読めます

自作のニュースレターフォームの登録を MailerLite に送る

自作の登録フォームを MailerLite につなぎます。リレーが新しい読者をダブルオプトインのために未確認として追加し、購読者に二度確認を求めることはありません。

Preet SutharIkiform 創業者

自作のフォームを Ikiform に送信し、Webhook を MailerLite の API を呼ぶリレーに向けます。リレーはまずアドレスを検索します。新しい読者は unconfirmed として作成され、API の設定がオンなら MailerLite がダブルオプトインのメールを送ります。既存の購読者には新しいグループが追加されるだけです。この検索があるからこそ、確認済みの購読者が再び確認を求められずに済みます。

MailerLite で異なる点

MailerLite の購読者作成のエンドポイントは upsert です。/api/subscribers への 1 回の POST で新しい購読者を作成するか既存の購読者を更新し、201 か 200 を返します。破壊的ではないので、省略したグループやフィールドは元のままです。落とし穴は status です。アカウント設定の Double opt-in for API and integrations がオンなら、MailerLite は API で追加された新しい購読者に確認メールを送ります。既存のアクティブな購読者や配信停止した購読者に送られるのは、呼び出しで status: "unconfirmed" を送ったときだけです。

MailerLite のヘルプによると、既存のアクティブな購読者や配信停止した購読者には、呼び出しに "status": "unconfirmed" が含まれない限り確認メールは送られません。つまり、常に unconfirmed を送るリレーでは、熱心な読者が再登録するたびに新たに確認を求めることになります。解決策は、送る内容を決める前にアドレスを検索することです。

リレーが MailerLite に送る内容を決める方法Ikiform の Webhook署名を検証済み購読者を GETメールアドレスで404:新規未確認として作成アクティブグループだけ追加配信停止済みそのままにする見つからない見つかったリレーが MailerLite に送る内容を決める方法Ikiform の Webhook署名を検証済み購読者を GETメールアドレスで404:新規未確認として作成アクティブグループだけ追加配信停止済みそのままにする見つからない見つかった
  1. リレーが Ikiform の Webhook を検証します。
  2. MailerLite にメールアドレスで購読者を問い合わせます。
  3. 見つからない場合:ステータス unconfirmed と設置場所のグループで作成し、MailerLite が確認メールを送ります。
  4. 見つかり、アクティブか未確認の場合:ステータスなしでグループを追加するので、確認の状態は何も変わりません。
  5. 見つかり、配信停止済みの場合:何もしないので、登録が配信停止をひそかに取り消すことはありません。
1 件の登録で API の呼び出しは 2 回。2 回目は 1 回目の結果によって決まります。

MailerLite を設定する

  1. MailerLite で購読の設定を開き、Double opt-in for API and integrations をオンにします。確認メールはそこで編集します。MailerLite は API で追加されたすべての購読者に同じメールを送ります。
  2. 「Website footer」や「Blog posts」のように設置場所ごとにグループを作り、各グループの id を控えておきます。
  3. Integrations で API トークンを作成し、リレーの環境変数用に保管しておきます。
MailerLite の料金の FAQ によると、最も安いプランでは API へのアクセスが制限されています。頼る前に、自分のプランでトークンが購読者を作成できるかテストしてください。

リレー

これは Express のサーバーです。署名は Ikiform が送ったバイト列そのものが対象で、先に JSON を解析すると変わってしまうので、生のボディをテキストとして読みます。

server.ts
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 が得られる場所ならどこにでもデプロイし、その URL を Ikiform の Webhooks に追加して、whsec_ のシークレットを IKIFORM_WEBHOOK_SECRET に入れます。リトライとペイロードについては Webhook のドキュメントに、データベースを使った同じ Express の構成は自前のサーバーにあります。

MailerLite の作成の呼び出しは subscribed_at、ip_address、opted_in_at、optin_ip を受け付けます。IP のフィールドは省いてください。Ikiform の Webhook には訪問者の IP アドレスもそのハッシュも含まれないので、リレーには送るべき正しい値がなく、でっち上げた値はないよりも悪いからです。

登録の記録は Ikiform に残ります。送信データには consent: true が、タイムスタンプ、オリジン、リファラー、国と一緒に保存されます。確認の記録は MailerLite にあり、読者がリンクをクリックした日時が記録されます。この 2 つで、いつチェックボックスにチェックが入り、いつ本人のアドレスであることが確かめられたかを示せます。必要なときは Submissions から Ikiform 側を CSV でエクスポートしてください。

レート制限

MailerLite の API 全体の上限は 1 分あたり 120 リクエストです。リレーは 1 件の登録につき 2 回呼び出すので、1 分あたり約 60 件の登録が収まります。それを超えると MailerLite は 429 を返し、リレーは Retry-After 付きの 503 を返し、Ikiform は同じ配信をあとでリトライします。

MailerLite の応答リレーの応答Ikiform
201 または 200204配信成功
429Retry-After 付きの 503待ってからリトライ
401502リトライ。トークンを直してから、失敗した配信で Retry now を使う
422エラー付きの 502リトライ。たいていはグループの id かフィールド名の間違い

ハマりどころ

  • status: "active" は送らない。 その人を購読済みとして扱いますが、それでも確認メールが送られるかどうかは MailerLite のドキュメントに書かれていません。送るステータスでテストしてください。
  • 再購読は選択の問題。 MailerLite には、配信停止した人向けの resubscribe フラグがあります。フォームでそう明記し、読者が再び購読することをわかっている場合にだけ送ってください。
  • 設定のオン・オフが重要。 MailerLite が API での登録に確認メールを送るのは、Double opt-in for API and integrations がオンのときだけです。
  • 確認メールは 1 つ。 API のダブルオプトインのメールは、API でのすべての登録で共通です。設置場所ごとに文面を変えたいなら、MailerLite のフォームを分けて使ってください。
  • Ikiform での重複。 Ikiform は受け付けたすべての送信を保持し、MailerLite はメールアドレスごとに 1 人の購読者を保持します。購読者数は Ikiform のダッシュボードではなく MailerLite で数えてください。

動作を確認する

  1. Ikiform でエンドポイントからテストを送ります。配信には 204 が表示されます。
  2. 自分の新しいアドレスで購読します。MailerLite ではフッターのグループに未確認の購読者として表示され、確認メールが届きます。
  3. 確認してから、ブログ記事からもう一度購読します。購読者はアクティブのままで 2 つ目のグループに加わり、新しい確認メールは届きません。
  4. MailerLite でそのアドレスを配信停止にし、もう一度購読します。配信ログに配信停止のメモが表示され、MailerLite では配信停止のままです。

よくある質問

手順で詰まったときや、あなたの状況に合うガイドがほしいときはメールでどうぞ: hello@ikiform.com