This workflow follows the Emailsend → Google Sheets recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "SMS+Mail Campa\u00f1a - WF2 Envio Mail (30 min despues del SMS)",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "*/10 * * * *"
}
]
}
},
"id": "schedule_trigger",
"name": "Schedule cada 10 min",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
0,
300
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "1",
"name": "GOOGLE_SHEET_ID",
"type": "string",
"value": "REEMPLAZAR - ID del ENVIO_SMS_MAIL_V2 en Google Sheets"
},
{
"id": "2",
"name": "N8N_BASE_URL",
"type": "string",
"value": "https://devn8n.gruporoisa.com.ar"
},
{
"id": "3",
"name": "TEMPLATES_DIR",
"type": "string",
"value": "REEMPLAZAR - carpeta accesible por n8n con plantilla_alta.html/plantilla_media.html/plantilla_baja.html"
},
{
"id": "4",
"name": "MAIL_FROM",
"type": "string",
"value": "ventasdoctored@doctored.com.ar"
}
]
},
"options": {}
},
"id": "set_config",
"name": "Config",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
220,
300
],
"notesInFlow": true,
"notes": "TEMPLATES_DIR debe ser accesible por el proceso de n8n (mismo host o volumen montado). Si n8n corre en otro servidor, reemplazar 'Leer Template HTML' por un HTTP Request a donde esten hosteados los .html."
},
{
"parameters": {
"operation": "read",
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.GOOGLE_SHEET_ID }}"
},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Control_Data"
},
"options": {}
},
"id": "read_control_data",
"name": "Leer Estado Campa\u00f1a",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
440,
300
],
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const rows = $input.all().map(i => i.json);\nconst estado = (rows.find(r => r.clave === 'Estado_Campana') || {}).valor || 'Pausada';\nconst activa = estado.toString().trim().toLowerCase() === 'activa';\nreturn [{ json: { estadoCampana: estado, activa } }];"
},
"id": "check_estado",
"name": "Chequear Estado Campa\u00f1a",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
660,
300
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "c1",
"leftValue": "={{ $json.activa }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "if_activa",
"name": "\u00bfCampa\u00f1a Activa?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
880,
300
]
},
{
"parameters": {},
"id": "noop_pausada",
"name": "Campa\u00f1a Pausada - No enviar",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
1100,
500
]
},
{
"parameters": {
"operation": "read",
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Config').first().json.GOOGLE_SHEET_ID }}"
},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Plantillas"
},
"options": {}
},
"id": "read_plantillas",
"name": "Leer Plantillas",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
1100,
160
],
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "read",
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Config').first().json.GOOGLE_SHEET_ID }}"
},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Plantillas_Mail"
},
"options": {}
},
"id": "read_plantillas_mail",
"name": "Leer Plantillas_Mail",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
1100,
260
],
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "read",
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Config').first().json.GOOGLE_SHEET_ID }}"
},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Contactos"
},
"options": {}
},
"id": "read_contacts",
"name": "Leer Contactos",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
1100,
360
],
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "function diffMin(fechaStr) {\n if (!fechaStr) return -1;\n const f = new Date(fechaStr.toString().replace(' ', 'T'));\n if (isNaN(f.getTime())) return -1;\n return (Date.now() - f.getTime()) / 60000;\n}\n\nconst tandas = $('Leer Plantillas').all().map(i => i.json).sort((a, b) => Number(a.tanda) - Number(b.tanda));\n// Plantillas_Mail esta vinculada a la tanda (no al lead): cada tanda define que .html usar.\nconst plantillasMail = $('Leer Plantillas_Mail').all().map(i => i.json);\n\nconst out = [];\nfor (const item of $input.all()) {\n const j = item.json;\n if ((j['desuscripto'] || '').toString().trim().toLowerCase() === 'si') continue;\n if (!j.email) continue;\n\n for (const t of tandas) {\n const n = Number(t.tanda);\n const envioN = (j['Envio' + n] || '').toString().trim();\n const emailEnvioN = (j['EmailEnvio' + n] || '').toString().trim();\n if (envioN !== 'Si') continue; // el SMS de esta tanda no se mando (o fallo)\n if (emailEnvioN !== '') continue; // el mail de esta tanda ya se envio\n\n const delayMin = Number(t['delay_mail_min'] || 30);\n if (diffMin(j['FechaEnvio' + n]) < delayMin) continue; // no paso la ventana de espera\n\n const plantillaMail = plantillasMail.find(p => Number(p.tanda) === n);\n if (!plantillaMail || !plantillaMail.archivo_template) continue; // tanda sin template de mail configurado -> no se envia\n\n out.push({\n json: {\n row_number: j.row_number,\n nombre: j.nombre || '',\n email: j.email,\n codigo: j.codigo,\n tanda: n,\n archivo_template: plantillaMail.archivo_template\n }\n });\n break; // 1 mail por corrida por lead; las tandas restantes se procesan en la proxima corrida\n }\n}\nreturn out;"
},
"id": "filter_candidatos_mail",
"name": "Generar candidatos mail",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1320,
300
]
},
{
"parameters": {
"operation": "read",
"fileSelector": "={{ $('Config').first().json.TEMPLATES_DIR }}/{{ $json.archivo_template }}"
},
"id": "read_template_file",
"name": "Leer Template HTML",
"type": "n8n-nodes-base.readWriteFile",
"typeVersion": 1,
"position": [
1540,
300
],
"notesInFlow": true,
"notes": "Lee el .html en disco. Requiere que n8n tenga acceso al filesystem donde estan plantilla_alta/media/baja.html."
},
{
"parameters": {
"operation": "text",
"binaryPropertyName": "data"
},
"id": "extract_template_text",
"name": "Extraer Texto Template",
"type": "n8n-nodes-base.extractFromFile",
"typeVersion": 1,
"position": [
1760,
300
]
},
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const j = $json;\nconst n8nBase = $('Config').first().json.N8N_BASE_URL;\nconst unsubUrl = `${n8nBase}/webhook/mail-baja/${j.codigo}`;\nconst clickUrl = `${n8nBase}/webhook/mail-click/${j.codigo}-${j.tanda}`;\n\nlet html = j.text || j.data || '';\nhtml = html.replace(/\\[email\\]/g, j.email);\n// Reemplaza el href del link \"quiero desuscribirme\" (identificado por su id fijo en el template).\nhtml = html.replace(/(href=\")[^\"]*(\"[^>]*id=\"unlayer-custom-tool-footer-unsubscribe-link\")/, `$1${unsubUrl}$2`);\n// Reemplaza el href del boton CTA principal (identificado por su clase fija en el template).\nhtml = html.replace(/(href=\")[^\"]*(\"[^>]*class=\"v-button\")/, `$1${clickUrl}$2`);\n\nreturn { json: { row_number: j.row_number, tanda: j.tanda, email: j.email, html } };"
},
"id": "armar_mail",
"name": "Armar Mail (placeholders + tracking)",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1980,
300
],
"notesInFlow": true,
"notes": "El matching de 'class=\"v-button\"' y del id de desuscripcion depende del markup actual de plantilla_alta/media/baja.html. Revisar si se edita el dise\u00f1o del template en el editor de mails."
},
{
"parameters": {
"fromEmail": "={{ $('Config').first().json.MAIL_FROM }}",
"toEmail": "={{ $json.email }}",
"subject": "En DoctoRED queremos ayudarte a encontrar tu plan ideal",
"html": "={{ $json.html }}",
"options": {}
},
"id": "send_mail",
"name": "Enviar Mail (SMTP doctored.dyndns.org)",
"type": "n8n-nodes-base.emailSend",
"typeVersion": 2.1,
"position": [
2200,
300
],
"credentials": {
"smtp": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "c1",
"leftValue": "={{ $json.messageId }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "if_mail_success",
"name": "Envio Mail OK?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
2420,
300
]
},
{
"parameters": {
"jsCode": "const j = $('Armar Mail (placeholders + tracking)').item.json;\nconst n = j.tanda;\nconst out = { row_number: j.row_number };\nout['EmailEnvio' + n] = new Date().toISOString().slice(0, 19).replace('T', ' ');\nreturn [{ json: out }];"
},
"id": "prep_update_mail_ok",
"name": "Preparar Update - Exito",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2640,
220
]
},
{
"parameters": {
"operation": "update",
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Config').first().json.GOOGLE_SHEET_ID }}"
},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Contactos"
},
"columns": {
"mappingMode": "autoMapInputData",
"matchingColumns": [
"row_number"
],
"value": {}
},
"options": {}
},
"id": "update_success",
"name": "Actualizar fila - Exito",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
2860,
220
],
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {},
"id": "noop_mail_fail",
"name": "No marcar - reintenta proxima corrida",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
2640,
400
],
"notesInFlow": true,
"notes": "No se escribe EmailEnvio{N}: al quedar vacio, la proxima corrida de este mismo workflow lo vuelve a intentar."
}
],
"connections": {
"Schedule cada 10 min": {
"main": [
[
{
"node": "Config",
"type": "main",
"index": 0
}
]
]
},
"Config": {
"main": [
[
{
"node": "Leer Estado Campa\u00f1a",
"type": "main",
"index": 0
}
]
]
},
"Leer Estado Campa\u00f1a": {
"main": [
[
{
"node": "Chequear Estado Campa\u00f1a",
"type": "main",
"index": 0
}
]
]
},
"Chequear Estado Campa\u00f1a": {
"main": [
[
{
"node": "\u00bfCampa\u00f1a Activa?",
"type": "main",
"index": 0
}
]
]
},
"\u00bfCampa\u00f1a Activa?": {
"main": [
[
{
"node": "Leer Plantillas",
"type": "main",
"index": 0
},
{
"node": "Leer Plantillas_Mail",
"type": "main",
"index": 0
},
{
"node": "Leer Contactos",
"type": "main",
"index": 0
}
],
[
{
"node": "Campa\u00f1a Pausada - No enviar",
"type": "main",
"index": 0
}
]
]
},
"Leer Contactos": {
"main": [
[
{
"node": "Generar candidatos mail",
"type": "main",
"index": 0
}
]
]
},
"Generar candidatos mail": {
"main": [
[
{
"node": "Leer Template HTML",
"type": "main",
"index": 0
}
]
]
},
"Leer Template HTML": {
"main": [
[
{
"node": "Extraer Texto Template",
"type": "main",
"index": 0
}
]
]
},
"Extraer Texto Template": {
"main": [
[
{
"node": "Armar Mail (placeholders + tracking)",
"type": "main",
"index": 0
}
]
]
},
"Armar Mail (placeholders + tracking)": {
"main": [
[
{
"node": "Enviar Mail (SMTP doctored.dyndns.org)",
"type": "main",
"index": 0
}
]
]
},
"Enviar Mail (SMTP doctored.dyndns.org)": {
"main": [
[
{
"node": "Envio Mail OK?",
"type": "main",
"index": 0
}
]
]
},
"Envio Mail OK?": {
"main": [
[
{
"node": "Preparar Update - Exito",
"type": "main",
"index": 0
}
],
[
{
"node": "No marcar - reintenta proxima corrida",
"type": "main",
"index": 0
}
]
]
},
"Preparar Update - Exito": {
"main": [
[
{
"node": "Actualizar fila - Exito",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleSheetsOAuth2Apismtp
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
SMS+Mail Campaña - WF2 Envio Mail (30 min despues del SMS). Uses googleSheets, readWriteFile, emailSend. Scheduled trigger; 18 nodes.
Source: https://github.com/martinrodrigogerez/quickcampaign_sms_mail/blob/13e6d1b38329da388efeb07c608d1048e363fc47/n8n/WF2_envio_mail.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Automate personalized email campaigns using a Google Sheets contact list, a Google Docs template, and SMTP delivery. Google Docs is used as the email template with variables: , , , . Google Sheet cont
Automatización de Email Marketing. Uses googleSheets, emailSend. Scheduled trigger; 4 nodes.
Workflow-Lead-Summary. Uses googleSheets, emailSend. Scheduled trigger; 3 nodes.
Lead Gen -> Outreach -> Reply Digest. Uses googleSheets, httpRequest, emailSend, emailReadImap. Event-driven trigger; 36 nodes.
This workflow runs on scheduled weekly and monthly triggers to generate unified marketing performance reports. It processes multiple websites by collecting analytics data, paid ads performance, and CR