Guardar correos de Gmail en Google Sheets sin duplicar mensajes
Los disparadores de correo pueden volver a entregar un mensaje cuando el workflow se reanuda, se cambia el filtro o se repite una ejecución. La forma práctica de hacer el registro idempotente (repetir la misma entrada sin crear otra fila) es guardar el message_id del correo y pedir a Google Sheets que agregue o actualice la fila que tenga esa misma clave.
Este tutorial registra metadatos mínimos: identificador, hilo, remitente, asunto, fecha y un fragmento corto. No guarda el cuerpo completo ni archivos adjuntos.
Estado de verificación: la prueba controlada real se completó el 13 de septiembre de 2026 con n8n 2.22.5. Gmail capturó el mensaje DEMO con el snippet
Mensaje de prueba controlada --, sin firma personal. La primera ejecución agregó una fila aCorreoLogy la repetición serial de la captura real fijada con Pin data conservó una sola fila con el mismomessage_idy contenido idéntico. La etiquetaFlujoteca-DEMO-procesadoquedó aplicada; la respuesta de Gmail conservóUNREAD. Después se desactivaron las escrituras y el workflow quedó sin publicar. Estos resultados no acreditan seguridad ante ejecuciones simultáneas.
Resultado que obtendrás
- Un workflow
Gmail Trigger → Code → Google Sheetsque conserva una fila pormessage_id. - Un filtro de búsqueda acotado para no leer el buzón completo durante la comprobación.
- Una operación Append or Update Row que actualiza el registro si el mismo mensaje vuelve a aparecer.
Antes de comenzar
- Una instancia de n8n y una cuenta de Gmail de prueba. No uses el buzón personal o de clientes para la primera ejecución.
- Una cuenta de Google con permiso de edición sobre un documento de prueba.
- Una pestaña llamada
CorreoLogcon estas columnas, en este orden:message_id | thread_id | de | asunto | recibido_en | snippet | estado - Un único correo de prueba enviado desde un buzón controlado, con
[DEMO]en el asunto. El workflow rechazará cualquier elemento cuyomessage_idesté vacío. No incluyas contraseñas, datos financieros ni información personal. - La credencial de Google para Gmail y Sheets se autoriza en n8n. No copies client secrets, tokens ni cookies al workflow.
Flujo completo
Gmail Trigger: Message Received
|
v
Code: normalizar metadatos
|
v
Google Sheets: Append or Update Row
(Column to Match On = message_id)
El disparador consulta Gmail periódicamente, por lo que no es una garantía de entrega instantánea. Append or Update Row compara la clave que indiques; si message_id ya existe, actualiza esa fila. No uses el asunto como clave: dos mensajes distintos pueden tener el mismo asunto.
Paso 1: preparar la hoja
- Crea un documento de prueba y una pestaña
CorreoLog. - Escribe los encabezados
message_id,thread_id,de,asunto,recibido_en,snippetyestadoen la primera fila. - No agregues filas con
message_idvacío: el nodo Code las rechaza antes de llegar a Sheets. - Comprueba que la cuenta que autorizarás en n8n puede editar el documento.
La clave message_id viene de Gmail y es distinta de thread_id: varios mensajes pueden pertenecer al mismo hilo. Conserva ambas para poder auditar qué mensaje produjo la fila.
Paso 2: configurar Gmail Trigger
Añade Gmail Trigger y selecciona el evento Message Received. Configura:
- La credencial de Gmail de una cuenta de prueba.
- Search, si está disponible, con
subject:"[DEMO]"para limitar la consulta. Verifica que el correo de prueba use exactamente ese marcador. - Simplify, activado, para trabajar con campos de mensaje manejables. Si tu versión entrega otros nombres, inspecciona una ejecución y adapta el Code del paso siguiente.
- El límite de mensajes por sondeo más bajo que permita tu prueba; un mensaje es suficiente.
El trigger puede consultar mensajes no leídos según la configuración de tu versión. No añadas todavía nodos que marquen mensajes como leídos, borren mensajes o agreguen etiquetas: son mutaciones adicionales de Gmail y requieren revisar permisos y recuperación.
Paso 3: normalizar los campos
Conecta un nodo Code. Déjalo en Run Once for All Items y usa este código defensivo. Acepta nombres simplificados y cabeceras comunes; revisa la entrada del trigger si tu cuenta devuelve una forma diferente.
return $input.all().map((item) => {
const input = item.json;
const headers = input.payload?.headers ?? [];
function header(name) {
const wanted = name.toLowerCase();
const found = headers.find((candidate) =>
String(candidate.name ?? '').toLowerCase() === wanted
);
return found?.value ?? '';
}
const messageId = String(
input.id ?? input.messageId ?? input.message_id ?? ''
).trim();
const threadId = String(input.threadId ?? input.thread_id ?? '').trim();
const fromValue = input.From ?? input.from ?? header('From');
const from = typeof fromValue === 'object'
? String(fromValue.value?.[0]?.address ?? '').trim()
: String(fromValue).trim();
const subject = String(input.Subject ?? input.subject ?? header('Subject')).trim();
const received = String(
input.internalDate ?? input.date ?? input.received_at ?? header('Date')
).trim();
const snippet = String(input.snippet ?? input.textPlain ?? '').trim().slice(0, 500);
if (!messageId) {
throw new Error('Gmail no entregó un message_id; revisa la salida del trigger');
}
return {
json: {
message_id: messageId,
thread_id: threadId,
de: from,
asunto: subject,
recibido_en: received,
snippet,
estado: 'recibido',
},
};
});
El límite de 500 caracteres es una decisión de privacidad y de tamaño, no una propiedad de Gmail. Si necesitas conservar el cuerpo, define antes una política de retención y de acceso; no lo agregues a este ejemplo por defecto.
Paso 4: actualizar o agregar la fila
Conecta un nodo Google Sheets al Code. En la plantilla este nodo de escritura está desactivado; mantenlo así mientras configuras el documento y la credencial, y habilítalo solo para la prueba controlada:
- Selecciona la credencial de Google que tenga acceso al documento de prueba.
- Elige el documento y la pestaña
CorreoLog. - Selecciona la operación Append or Update Row.
- En Column to Match On, selecciona
message_id. - Usa Map Automatically si aparece esa opción y revisa que las siete claves del Code coincidan con los encabezados.
- No selecciones
asunto,thread_idnidecomo clave de coincidencia.
La primera llegada debe agregar una fila. En reprocesos seriales, si n8n vuelve a entregar el mismo mensaje, la misma clave debe conducir a una actualización, no a una segunda fila. Si el message_id está vacío, el Code detiene el flujo y no debe llegar a Sheets. Si quieres detectar manualmente una posible colisión, deja message_id visible y no lo trunques.
Esta configuración no constituye un bloqueo transaccional: dos ejecuciones simultáneas pueden consultar la hoja antes de que la otra escriba. Mantén una sola ejecución activa durante la prueba y, antes de automatizar en paralelo, añade una reserva o mecanismo de serialización adecuado.
Cómo comprobar que funciona
La prueba controlada real del 13 de septiembre de 2026 se ejecutó con un único mensaje DEMO, el filtro exacto por asunto y n8n 2.22.5. El snippet observado fue Mensaje de prueba controlada --, sin firma personal. La primera ejecución terminó con Workflow executed successfully y CorreoLog pasó de solo encabezados a encabezados más una fila. La repetición serial utilizó el mismo item de la captura real fijada con Pin data; también terminó correctamente, conservó el contenido de la fila y dejó una sola fila con el mismo message_id. La etiqueta Flujoteca-DEMO-procesado se aplicó al mensaje; la respuesta de Gmail incluyó su identificador junto con UNREAD. Al finalizar, los nodos de escritura y etiqueta quedaron desactivados y el workflow permaneció sin publicar.
Para repetir esta comprobación en una instancia propia, usa este orden con un único mensaje:
- Activa el modo de prueba del trigger o ejecuta el workflow según la modalidad de tu instancia.
- Envía desde el buzón controlado un correo con asunto
[DEMO] prueba n8ny sin información sensible. - Espera un ciclo de sondeo y revisa la entrada del Gmail Trigger. Confirma que existe un
idomessageId. - Verifica que Code produzca un
message_idno vacío y unsnippetde como máximo 500 caracteres. Si el ID está vacío, debe fallar con un error explícito y no escribir nada. - Confirma en Sheets que existe una sola fila con ese
message_id. - Cuando la primera ejecución haya terminado, repite el mismo mensaje solo para probar la idempotencia serial. El número de filas debe seguir siendo uno; el valor de
estadopuede actualizarse.
La primera prueba requiere un entorno real porque Gmail Trigger y Google Sheets necesitan OAuth. Si no dispones todavía de credenciales, puedes revisar el Code con el siguiente objeto fijado en el editor, pero eso no valida la autorización ni la operación de Sheets:
{
"id": "demo-message-001",
"threadId": "demo-thread-001",
"From": "remitente@example.com",
"Subject": "[DEMO] prueba n8n",
"Date": "2026-08-27T10:00:00-06:00",
"snippet": "Solicitud de demostración sin datos personales"
}
Etapa opcional: etiquetar el mensaje procesado
La plantilla incluye Añadir etiqueta procesado, desactivado por defecto. Esta etapa cambia el buzón y debe probarse solo con el mensaje DEMO autorizado, después de verificar que Sheets guardó su fila. No sustituye la comparación por message_id.
- Crea una etiqueta de prueba, por ejemplo
Flujoteca-DEMO-procesado, en el buzón elegido. - Selecciona la credencial Gmail de ese buzón y revisa los permisos de modificación de etiquetas antes de autorizarla.
- En el nodo Gmail, configura Resource: Message, Operation: Add Label y Message ID:
={{ $json.message_id }}. Comprueba que la salida real de Sheets conserva ese identificador. - En Label Names or IDs, selecciona la etiqueta de prueba y sustituye
REPLACE_LABEL_IDen la copia importada. No copies su ID real a la plantilla distribuible. - Habilita el nodo solo para la prueba. Verifica en Gmail que el mensaje correcto tiene la etiqueta y repite serialmente el mismo mensaje:
CorreoLogdebe mantener una sola fila. - Al terminar, deja el workflow inactivo y los nodos de escritura y etiqueta desactivados. Si la etiqueta falla después de guardar la fila, inspecciona ambos destinos antes de repetir.
Este procedimiento sigue la operación Add Label de n8n.
Errores habituales
El trigger no encuentra el correo
- Causa probable: el filtro de búsqueda no coincide, el mensaje aún no entró en el ciclo de sondeo o el trigger está configurado para otro estado de lectura.
- Comprobación: busca manualmente
[DEMO]en Gmail y revisa la última ejecución de n8n. - Corrección mínima: usa temporalmente un filtro más amplio en el buzón de prueba y reduce después la consulta; no proceses todo el buzón como primera prueba.
Code falla por message_id vacío
- Causa probable: el trigger entrega el identificador con otro nombre o se desactivó la salida simplificada.
- Comprobación: abre la entrada del nodo y localiza
id,messageIdo la cabeceraMessage-ID. - Corrección mínima: el flujo debe detenerse; inspecciona la salida real del trigger y adapta solo la expresión de extracción para obtener una clave no vacía antes de volver a probar.
Cada ejecución agrega una fila nueva
- Causa probable: se eligió Append Row, la columna de coincidencia está vacía o se está usando
thread_iden vez demessage_id. - Comprobación: compara literalmente los valores de
message_idy revisa la operación del nodo. - Corrección mínima: selecciona Append or Update Row, elige
message_idcomo columna de coincidencia y vuelve a ejecutar el mismo elemento.
El nodo de Sheets devuelve 403
- Causa probable: la credencial no tiene acceso de edición o el consentimiento de Google no incluye el documento.
- Comprobación: abre el documento con la cuenta autorizada y revisa la configuración de credenciales en n8n.
- Corrección mínima: autoriza una cuenta de prueba con acceso de edición; no compartas el documento públicamente para resolver el error.
Se guarda demasiado contenido del correo
- Causa probable: se desactivó Simplify o se mapeó el payload completo en Sheets.
- Comprobación: revisa qué campos entran a Google Sheets.
- Corrección mínima: conserva el Code de normalización y mapea solo las siete columnas del contrato.
Seguridad y privacidad
- Gmail contiene información de alta sensibilidad. Usa una cuenta de prueba, un filtro explícito y el mínimo conjunto de campos.
- Las credenciales OAuth y sus alcances dependen del nodo y de la modalidad de n8n. Revisa la pantalla de consentimiento y no guardes tokens ni client secrets en el workflow.
- Un
snippetpuede incluir datos personales aunque sea corto. En producción, considera omitirlo o redactarlo antes de almacenarlo. - No borres ni respondas correos durante esta prueba. La etiqueta opcional requiere el mensaje y los permisos autorizados descritos arriba; el nodo de etiqueta y el nodo de escritura en Sheets se entregan desactivados.
- Restringe el acceso al documento de Sheets y define retención. Una hoja compartida puede exponer remitentes y asuntos.
- La garantía de no duplicación depende de que
message_idsea estable, no vacío y se compare en la misma columna. Revisa colisiones antes de procesar lotes.