\r\n
\r\n \r\n\r\n \/\/ Ejemplo de validaci\u00f3n\r\nconst emailRegex = \/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$\/;\r\nif (!emailRegex.test(email)) {\r\n return { error: 'Formato de email no v\u00e1lido' };\r\n}\r\n\r\n\/\/ Almacenar suscripci\u00f3n pendiente\r\nawait db.subscriptions.create({\r\n email: email,\r\n status: 'pending',\r\n created_at: new Date(),\r\n ip_address: req.ip\r\n});\r\n <\/code>\r\n <\/pre>\r\n <\/div>\r\n <\/div>\r\n <\/div>\r\n\nPaso 2: Crear un enlace de confirmaci\u00f3n seguro<\/strong><\/h3>\n\nLa seguridad es fundamental.<\/strong> Nunca utilices MD5 o hashes sencillos. En lugar de eso aplica una seguridad de token adecuada:<\/p>\n\nRequisitos de los tokens<\/strong><\/h4>\n\n\nCaducidad de corta duraci\u00f3n<\/strong> (30-60 minutos).<\/li>\n\n\n\nUn solo uso<\/strong> con protecci\u00f3n contra repeticiones.<\/li>\n\n\n\nFirmado criptogr\u00e1ficamente<\/strong> (HMAC-SHA256 o JWT).<\/li>\n\n\n\nIncluir declaraciones:<\/strong> email, issued_at, expires_at, nonce\/jti.<\/li>\n<\/ul>\n\nEjemplo de creaci\u00f3n de tokens<\/strong><\/h4>\n \r\n
\r\n\r\n \r\n \r\n \r\n \r\n Javascript <\/button>\r\n <\/li>\r\n\r\n \r\n \r\n <\/ul>\r\n \r\n\r\n \r\n
\r\n
\r\n \r\n\r\n const jwt = require('jsonwebtoken');\r\nconst crypto = require('crypto');\r\n\r\nfunction createConfirmationToken(email) {\r\n const nonce = crypto.randomBytes(16).toString('hex');\r\n const payload = {\r\n email: email,\r\n nonce: nonce,\r\n iat: Math.floor(Date.now() \/ 1000),\r\n exp: Math.floor(Date.now() \/ 1000) + (60 * 60) \/\/ 1 hora\r\n };\r\n\r\n return jwt.sign(payload, process.env.JWT_SECRET);\r\n}\r\n\r\nconst token = createConfirmationToken('usuario@ejemplo.com');\r\nconst confirmUrl = `https:\/\/tuapp.com\/confirm?token=${token}`;\r\n <\/code>\r\n <\/pre>\r\n <\/div>\r\n <\/div>\r\n <\/div>\r\n\nPaso 3: Enviar el email de confirmaci\u00f3n<\/strong><\/h3>\n\nUtiliza la API de env\u00edo v3.1 de Mailjet para enviar el correo electr\u00f3nico de confirmaci\u00f3n. Tienes dos opciones:<\/p>\n\n
Opci\u00f3n A: Utilizar una plantilla almacenada (recomendado)<\/strong><\/h4>\n\nEn primer lugar, crea tu plantilla en Mailjet y, a continuaci\u00f3n, env\u00edala:<\/p>\n
\r\n
\r\n\r\n \r\n \r\n \r\n \r\n Shell <\/button>\r\n <\/li>\r\n\r\n \r\n \r\n <\/ul>\r\n \r\n\r\n \r\n
\r\n
\r\n \r\n\r\n curl -s -X POST \\\r\n --user "$MJ_APIKEY_PUBLIC:$MJ_APIKEY_PRIVATE" \\\r\n https:\/\/api.mailjet.com\/v3.1\/send \\\r\n -H 'Content-Type: application\/json' \\\r\n -d '{\r\n "Messages":[\r\n {\r\n "From":{"Email":"no-reply@tudominio.com","Name":"Tu marca"},\r\n "To":[{"Email":"destinatario@ejemplo.com","Name":"Destinatario"}],\r\n "TemplateID": 123456,\r\n "TemplateLanguage": true,\r\n "Subject": "Por favor, confirma tu suscripci\u00f3n",\r\n "Variables": {\r\n "confirm_url": "https:\/\/tuapp.com\/confirm?token=...signed...",\r\n "first_name": "\u00c1lex"\r\n }\r\n }\r\n ]\r\n }'\r\n\r\n <\/code>\r\n <\/pre>\r\n <\/div>\r\n <\/div>\r\n <\/div>\r\n\nOpci\u00f3n B: Contenido en l\u00ednea (Sin plantilla)<\/strong><\/h4>\n \r\n
\r\n\r\n \r\n \r\n \r\n \r\n Shell <\/button>\r\n <\/li>\r\n\r\n \r\n \r\n <\/ul>\r\n \r\n\r\n \r\n
\r\n
\r\n \r\n\r\n curl -s -X POST \\\r\n --user "$MJ_APIKEY_PUBLIC:$MJ_APIKEY_PRIVATE" \\\r\n https:\/\/api.mailjet.com\/v3.1\/send \\\r\n -H 'Content-Type: application\/json' \\\r\n -d '{\r\n "Messages":[\r\n {\r\n "From":{"Email":"no-reply@tudominio.com","Name":"Tu marca"},\r\n "To":[{"Email":"destinatario@ejemplo.com"}],\r\n "Subject":"Confirma tu suscripci\u00f3n",\r\n "TextPart":"Haz clic aqu\u00ed para confirmar: {{var:confirm_url}}",\r\n "HTMLPart":"<p>\u00a1Ya casi est\u00e1! <a href="%5C%22%7B%7Bvar:confirm_url%7D%7D%5C%22">Confirma tu suscripci\u00f3n<\/a>.<\/p>",\r\n "TemplateLanguage": true,\r\n "Variables":{"confirm_url":"https:\/\/tuapp.com\/confirm?token=..."}\r\n }\r\n ]\r\n }'\r\n <\/code>\r\n <\/pre>\r\n <\/div>\r\n <\/div>\r\n <\/div>\r\n\nPaso 4: Gestionar los clics de confirmaci\u00f3n<\/strong><\/h3>\n\nTu punto de conexi\u00f3n de confirmaci\u00f3n debe verificar los tokens de forma segura:<\/p>\n
\r\n
\r\n\r\n \r\n \r\n \r\n \r\n Javascript <\/button>\r\n <\/li>\r\n\r\n \r\n \r\n <\/ul>\r\n \r\n\r\n \r\n
\r\n
\r\n \r\n\r\n app.get('\/confirm', async (req, res) => {\r\n const { token } = req.query;\r\n\r\n try {\r\n \/\/ Verifica la firma y la caducidad del token\r\n const decoded = jwt.verify(token, process.env.JWT_SECRET);\r\n\r\n \/\/ Comprueba si el token ya se ha utilizado\r\n const existingUse = await db.used_tokens.findOne({ \r\n token_hash: crypto.createHash('sha256').update(token).digest('hex') \r\n });\r\n\r\n if (existingUse) {\r\n return res.status(400).send('Token ya utilizado");\r\n }\r\n\r\n \/\/ Marcar token como utilizado\r\n await db.used_tokens.create({\r\n token_hash: crypto.createHash('sha256').update(token).digest('hex'),\r\n used_at: new Date()\r\n });\r\n\r\n \/\/ Proceder a la suscripci\u00f3n\r\n await subscribeToMailjet(decoded.email);\r\n\r\n res.send("\u00a1Suscripci\u00f3n confirmada correctamente!");\r\n\r\n } catch (error) {\r\n res.status(400).send('Enlace de confirmaci\u00f3n caducado o no v\u00e1lido");\r\n }\r\n});\r\n <\/code>\r\n <\/pre>\r\n <\/div>\r\n <\/div>\r\n <\/div>\r\n\nPaso 5: Suscribir a la lista de Mailjet<\/strong><\/h3>\n\nUna vez verificado el token, a\u00f1ade el contacto a tu lista de Mailjet:<\/p>\n\n
Crear o actualizar un contacto<\/strong><\/h4>\n \r\n
\r\n\r\n \r\n \r\n \r\n \r\n Shell <\/button>\r\n <\/li>\r\n\r\n \r\n \r\n <\/ul>\r\n \r\n\r\n \r\n
\r\n
\r\n \r\n\r\n curl -s -X POST \\\r\n --user "$MJ_APIKEY_PUBLIC:$MJ_APIKEY_PRIVATE" \\\r\n https:\/\/api.mailjet.com\/v3\/REST\/contact \\\r\n -H 'Content-Type: application\/json' \\\r\n -d '{\r\n "Email":"destinatario@ejemplo.com",\r\n "Name":"Nombre del destinatario"\r\n }'\r\n\r\n <\/code>\r\n <\/pre>\r\n <\/div>\r\n <\/div>\r\n <\/div>\r\n\nA\u00f1adir a la lista<\/strong><\/h4>\n \r\n
\r\n\r\n \r\n \r\n \r\n \r\n Shell <\/button>\r\n <\/li>\r\n\r\n \r\n \r\n <\/ul>\r\n \r\n\r\n \r\n
\r\n
\r\n \r\n\r\n curl -s -X POST \\\r\n --user "$MJ_APIKEY_PUBLIC:$MJ_APIKEY_PRIVATE" \\\r\n https:\/\/api.mailjet.com\/v3\/REST\/listrecipient \\\r\n -H 'Content-Type: application\/json' \\\r\n -d '{\r\n "ContactAlt":"destinatario@ejemplo.com",\r\n "ListID": 123456\r\n }'\r\n\r\n <\/code>\r\n <\/pre>\r\n <\/div>\r\n <\/div>\r\n <\/div>\r\n\nGuardar propiedades de contacto<\/strong><\/h4>\n \r\n
\r\n\r\n \r\n \r\n \r\n \r\n Shell <\/button>\r\n <\/li>\r\n\r\n \r\n \r\n <\/ul>\r\n \r\n\r\n \r\n
\r\n
\r\n \r\n\r\n curl -s -X PUT \\\r\n --user "$MJ_APIKEY_PUBLIC:$MJ_APIKEY_PRIVATE" \\\r\n https:\/\/api.mailjet.com\/v3\/REST\/contactdata\/destinatario@ejemplo.com \\\r\n -H 'Content-Type: application\/json' \\\r\n -d '{\r\n "Data":[\r\n {"Name":"first_name","Value":"\u00c1lex"},\r\n {"Name":"city","Value":"\u00c1vila"}\r\n ]\r\n }'\r\n <\/code>\r\n <\/pre>\r\n <\/div>\r\n <\/div>\r\n <\/div>\r\n\nNota:<\/strong> Define primero las propiedades personalizadas de los contactos utilizando el punto de conexi\u00f3n contactmetadata si a\u00fan no existen.<\/p>\n\nPaso 6: Enviar email de bienvenida (Opcional)<\/strong><\/h3>\n\nDespu\u00e9s de suscribir correctamente al contacto, env\u00eda un mensaje de correo electr\u00f3nico de bienvenida utilizando el mismo patr\u00f3n con la API de env\u00edos v3.1 del Paso 3.<\/p>\n\n
Paso 7: Configurar webhooks para el seguimiento de eventos<\/strong><\/h3>\n\nConfigura webhooks para recibir eventos en tiempo real para el mantenimiento continuo de la lista:<\/p>\n
\r\n
\r\n\r\n \r\n \r\n \r\n \r\n Shell <\/button>\r\n <\/li>\r\n\r\n \r\n \r\n <\/ul>\r\n \r\n\r\n \r\n
\r\n
\r\n \r\n\r\n curl -s -X POST \\\r\n --user "$MJ_APIKEY_PUBLIC:$MJ_APIKEY_PRIVATE" \\\r\n https:\/\/api.mailjet.com\/v3\/REST\/eventcallbackurl \\\r\n -H 'Content-Type: application\/json' \\\r\n -d '{\r\n "EventType": "sent",\r\n "Url": "https:\/\/tuapp.com\/webhooks\/mailjet"\r\n }'\r\n <\/code>\r\n <\/pre>\r\n <\/div>\r\n <\/div>\r\n <\/div>\r\n\nSigue estos eventos:<\/p>\n\n
\nsent<\/strong> (enviado), open<\/strong> (apertura), click<\/strong> (cliqueado) para m\u00e9tricas de interacci\u00f3n.<\/li>\n\n\n\nbounce<\/strong> (rebote), blocked<\/strong> (bloqueado), spam<\/strong> para problemas de entregabilidad.<\/li>\n\n\n\nunsub<\/strong> (bajas) para el cumplimiento normativo y el mantenimiento de la lista.<\/li>\n<\/ul>\n\nMejores pr\u00e1cticas de seguridad y cumplimiento normativo<\/strong><\/h2>\n\nHe aqu\u00ed algunas de las mejores pr\u00e1cticas m\u00e1s comunes.<\/p>\n\n
Registro de datos para el cumplimiento normativo<\/strong><\/h3>\n\nAlmacena la siguiente informaci\u00f3n para cumplir con el RGPD:<\/p>\n\n
\nHora de env\u00edo del formulario y direcci\u00f3n IP.<\/li>\n\n\n\n Cadena de agente de usuario.<\/li>\n\n\n\n Direcci\u00f3n de email introducida.<\/li>\n\n\n\n Marca de tiempo de confirmaci\u00f3n y direcci\u00f3n IP.<\/li>\n\n\n\n ID del token utilizado (nunca el token en bruto).<\/li>\n<\/ul>\n\nRecomendaciones de seguridad<\/strong><\/h3>\n\n\nNunca utilices MD5:<\/strong> Utiliza HMAC-SHA256 o JWT con la caducidad adecuada.<\/li>\n\n\n\nImplementa una limitaci\u00f3n de velocidad<\/strong> para evitar las inscripciones autom\u00e1ticas.<\/li>\n\n\n\nA\u00f1ade CAPTCHA<\/strong> para protecci\u00f3n adicional contra bots.<\/li>\n\n\n\nVerifica tu dominio<\/strong> con registros SPF\/DKIM para mejorar la entregabilidad.<\/li>\n\n\n\nAlmacena solo hashes de tokens:<\/strong> Nunca registres tokens sin procesar.<\/li>\n<\/ul>\n\nSoluci\u00f3n de problemas y preguntas frecuentes<\/strong><\/h2>\n\n\u00bfTienes m\u00e1s preguntas sobre este proceso? Consulta nuestras preguntas frecuentes.<\/p>\n\n
\u00bfPuedo reenviar emails de confirmaci\u00f3n?<\/strong><\/h3>\n\nS\u00ed. Crea un punto de conexi\u00f3n de tipo \u00abresend\u00bb que invalide el token antiguo y emita uno nuevo con una nueva caducidad.<\/p>\n\n
\u00bfY si el contacto ya existe?<\/strong><\/h3>\n\nPuedes volver a enviar confirmaciones. Al hacer clic, utiliza el punto de conexi\u00f3n listrecipient para asegurarte de que est\u00e1n en la lista correcta.<\/p>\n\n
\u00bfD\u00f3nde debo poner el token: en la cadena de consulta o en la ruta?<\/strong><\/h3>\n\nLa cadena de consulta es el lugar m\u00e1s t\u00edpico y m\u00e1s f\u00e1cil de analizar. Nunca registres tokens sin procesar en las anal\u00edticas.<\/p>\n\n
\u00bfDeber\u00eda utilizar en su lugar el Editor de formularios de Mailjet?<\/strong><\/h3>\n\nEl Editor de formularios es excelente para crear r\u00e1pidamente. Utiliza este enfoque con la API para experiencias personalizadas a gran escala.<\/p>\n\n
Recursos adicionales<\/strong><\/h2>\n\n