Formulario de suscripción a newsletter

Formulario de suscripción a newsletter6 min de lectura

Envía a MailerLite las suscripciones de tu propio formulario de newsletter

Conecta tu propio formulario de suscripción con MailerLite: un relay añade a los lectores nuevos como no confirmados para el doble opt-in y nunca pide a un suscriptor que confirme dos veces.

Preet SutharFundador, Ikiform

Envía tu propio formulario a Ikiform y apunta el webhook a un relay que llame a la API de MailerLite. El relay busca primero la dirección: un lector nuevo se crea como unconfirmed, lo que hace que MailerLite envíe su email de doble opt-in cuando el ajuste de la API está activado, y un suscriptor existente solo recibe el nuevo grupo. Esa búsqueda es lo que evita que a un suscriptor confirmado se le pida confirmar otra vez.

Qué cambia con MailerLite

El endpoint de crear suscriptor de MailerLite es un upsert: un POST a /api/subscribers crea un suscriptor nuevo o actualiza uno existente, y responde 201 o 200. No es destructivo, así que los grupos y campos que no incluyas se quedan como estaban. El problema es status. Con el ajuste de cuenta Double opt-in for API and integrations activado, MailerLite envía una confirmación a los nuevos suscriptores de la API. Un suscriptor existente activo o dado de baja solo la recibe cuando la llamada envía status: "unconfirmed".

La ayuda de MailerLite dice que los suscriptores existentes activos o dados de baja no reciben el email de confirmación a menos que la llamada incluya "status": "unconfirmed". Así que un relay que siempre envía unconfirmed convierte cada suscripción repetida de un lector fiel en una nueva petición de confirmación. La solución es buscar la dirección antes de decidir qué enviar:

Cómo decide el relay qué enviar a MailerLitewebhook deIkiformfirma verificadaGET subscriberpor email404: nuevocrear como unconfirmedactivoañadir solo el grupodado de bajano tocarno encontradoencontradoCómo decide el relay qué enviar a MailerLitewebhook deIkiformfirma verificadaGET subscriberpor email404: nuevocrear como unconfirmedactivoañadir soloel grupodado debajano tocarno encontradoencontrado
  1. El relay verifica el webhook de Ikiform.
  2. Pide a MailerLite el suscriptor por email.
  3. No existe: lo crea con estado unconfirmed y el grupo de la ubicación, y MailerLite envía el email de confirmación.
  4. Existe y está activo o sin confirmar: añade el grupo sin estado, así que nada de su confirmación cambia.
  5. Existe y se dio de baja: no hace nada, así que una suscripción nunca deshace en silencio una baja.
Dos llamadas a la API por suscripción, y la segunda depende de lo que encontró la primera.

Configura MailerLite

  1. En MailerLite, abre los ajustes de suscripción y activa Double opt-in for API and integrations. Edita ahí el email de confirmación; MailerLite envía el mismo a todos los suscriptores añadidos mediante la API.
  2. Crea un grupo por ubicación, como "Website footer" y "Blog posts", y apunta el id de cada grupo.
  3. En Integrations, crea un token de API y guárdalo para el entorno del relay.
Las preguntas frecuentes de precios de MailerLite dicen que el acceso a la API está limitado en su plan más bajo. Comprueba que el token puede crear suscriptores en tu plan antes de confiar en él.

El relay

Esto es un servidor Express. Lee el cuerpo sin procesar como texto, porque la firma cubre los bytes exactos que envió Ikiform, y analizar el JSON primero los cambiaría.

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 recibe el token como Authorization: Bearer. La URL base es https://connect.mailerlite.com/api.
  • GET /api/subscribers/{email} acepta un email o un id y responde 404 cuando el suscriptor no existe.
  • groups recibe ids de grupos que ya existen. Un id incorrecto se rechaza con 422, que el relay transmite como 502 con el mensaje de MailerLite.
  • fields.name es el campo de nombre por defecto de MailerLite. Los campos personalizados deben existir en MailerLite antes de enviarlos.
  • El 208 es una marca interna para un lector dado de baja. Ikiform ve 200 y una nota en el registro de entregas.

Despliégalo en cualquier sitio que te dé una URL HTTPS pública en el puerto 443, añade esa URL en Webhooks en Ikiform y pon el secreto whsec_ en IKIFORM_WEBHOOK_SECRET. La documentación de webhooks cubre los reintentos y el payload, y tu propio servidor tiene la misma configuración de Express con una base de datos.

La llamada de creación de MailerLite acepta subscribed_at, ip_address, opted_in_at y optin_ip. Deja fuera los campos de IP. El webhook de Ikiform nunca incluye la dirección IP del visitante ni su hash, así que el relay no tiene nada verdadero que enviar, y un valor inventado es peor que ninguno.

El registro de la suscripción se queda en Ikiform: el envío conserva consent: true con su marca de tiempo, origen, referrer y país. El registro de la confirmación vive en MailerLite, que anota cuándo hizo clic el lector en el enlace. Entre los dos puedes mostrar cuándo alguien marcó la casilla y cuándo demostró que la dirección era suya. Exporta la parte de Ikiform como CSV desde Submissions cuando la necesites.

Límites de frecuencia

El límite global de la API de MailerLite es de 120 peticiones por minuto. El relay hace dos por suscripción, así que caben unas 60 suscripciones por minuto. Por encima de eso, MailerLite responde 429, el relay responde 503 con Retry-After, e Ikiform vuelve a intentarlo más tarde con la misma entrega.

MailerLite respondeEl relay respondeIkiform
201 o 200204Entrega correcta
429503 con Retry-AfterEspera y reintenta
401502Reintenta. Arregla el token y luego usa Retry now en las entregas fallidas
422502 con los erroresReintenta. Normalmente es un id de grupo o un nombre de campo incorrecto

Errores comunes

  • No envíes status: "active". Marca a la persona como suscrita, y la documentación de MailerLite no dice si aun así sale una confirmación. Prueba con el estado que envíes.
  • Volver a suscribir es una decisión. MailerLite tiene un flag resubscribe para quienes se dieron de baja. Envíalo solo cuando tu formulario lo diga, y cuando el lector sepa que vuelve a darse de alta.
  • El interruptor importa. MailerLite solo envía un email de confirmación a las suscripciones de la API cuando Double opt-in for API and integrations está activado.
  • Un solo email de confirmación. El email de doble opt-in de la API es el mismo para todas las suscripciones de la API. Si necesitas un texto distinto por ubicación, usa formularios de MailerLite separados.
  • Duplicados en Ikiform. Ikiform guarda cada envío aceptado, mientras que MailerLite guarda un suscriptor por email. Cuenta los suscriptores en MailerLite, no en el panel de Ikiform.

Comprueba que funciona

  1. Envía una prueba desde el endpoint en Ikiform. La entrega muestra 204.
  2. Suscríbete con una dirección nueva tuya. MailerLite muestra el suscriptor como no confirmado en el grupo del footer, y llega el email de confirmación.
  3. Confírmalo y vuelve a suscribirte desde un post del blog. El suscriptor sigue activo, se une al segundo grupo y no recibe un nuevo email de confirmación.
  4. Da de baja esa dirección en MailerLite y suscríbete una vez más. El registro de entregas muestra la nota de baja, y MailerLite sigue mostrándolo como dado de baja.

Preguntas

¿Te atascaste en un paso o quieres una guía para tu caso? Escribe a hello@ikiform.com.