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 a CorreoLog y la repetición serial de la captura real fijada con Pin data conservó una sola fila con el mismo message_id y contenido idéntico. La etiqueta Flujoteca-DEMO-procesado quedó 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 Sheets que conserva una fila por message_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 CorreoLog con 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 cuyo message_id esté 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

  1. Crea un documento de prueba y una pestaña CorreoLog.
  2. Escribe los encabezados message_id, thread_id, de, asunto, recibido_en, snippet y estado en la primera fila.
  3. No agregues filas con message_id vacío: el nodo Code las rechaza antes de llegar a Sheets.
  4. 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:

  1. Selecciona la credencial de Google que tenga acceso al documento de prueba.
  2. Elige el documento y la pestaña CorreoLog.
  3. Selecciona la operación Append or Update Row.
  4. En Column to Match On, selecciona message_id.
  5. Usa Map Automatically si aparece esa opción y revisa que las siete claves del Code coincidan con los encabezados.
  6. No selecciones asunto, thread_id ni de como 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:

  1. Activa el modo de prueba del trigger o ejecuta el workflow según la modalidad de tu instancia.
  2. Envía desde el buzón controlado un correo con asunto [DEMO] prueba n8n y sin información sensible.
  3. Espera un ciclo de sondeo y revisa la entrada del Gmail Trigger. Confirma que existe un id o messageId.
  4. Verifica que Code produzca un message_id no vacío y un snippet de como máximo 500 caracteres. Si el ID está vacío, debe fallar con un error explícito y no escribir nada.
  5. Confirma en Sheets que existe una sola fila con ese message_id.
  6. 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 estado puede 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.

  1. Crea una etiqueta de prueba, por ejemplo Flujoteca-DEMO-procesado, en el buzón elegido.
  2. Selecciona la credencial Gmail de ese buzón y revisa los permisos de modificación de etiquetas antes de autorizarla.
  3. 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.
  4. En Label Names or IDs, selecciona la etiqueta de prueba y sustituye REPLACE_LABEL_ID en la copia importada. No copies su ID real a la plantilla distribuible.
  5. Habilita el nodo solo para la prueba. Verifica en Gmail que el mensaje correcto tiene la etiqueta y repite serialmente el mismo mensaje: CorreoLog debe mantener una sola fila.
  6. 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, messageId o la cabecera Message-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_id en vez de message_id.
  • Comprobación: compara literalmente los valores de message_id y revisa la operación del nodo.
  • Corrección mínima: selecciona Append or Update Row, elige message_id como 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 snippet puede 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_id sea estable, no vacío y se compare en la misma columna. Revisa colisiones antes de procesar lotes.

Fuentes oficiales