Pular para conteúdo

SEND_WITH_ATTACHMENT - Enviar Email com Anexos

O que é este Node?

O SEND_WITH_ATTACHMENT é o node responsável por enviar emails com arquivos anexados através da API do Gmail, suportando múltiplos anexos em diversos formatos (PDF, imagens, documentos, etc).

Por que este Node existe?

Compartilhar documentos por email é essencial para comunicação profissional, mas anexar arquivos via API é complexo devido à codificação MIME multipart. O SEND_WITH_ATTACHMENT existe para:

  1. Enviar documentos: PDFs, contratos, relatórios anexados automaticamente
  2. Múltiplos anexos: Enviar vários arquivos em um único email
  3. Diversos formatos: Suporta qualquer tipo de arquivo (PDF, DOCX, PNG, etc)
  4. Automação completa: Anexar arquivos gerados dinamicamente em fluxos

Como funciona internamente?

Quando o SEND_WITH_ATTACHMENT é executado, o sistema:

  1. Valida OAuth2: Confirma credenciais de autenticação
  2. Valida anexos: Verifica se attachments array está presente
  3. Cria boundary: Gera separador único para partes MIME
  4. Monta multipart: Estrutura email com corpo e anexos separados
  5. Codifica anexos: Converte cada anexo para base64
  6. Adiciona headers: Define Content-Type e Content-Disposition
  7. Envia via API: Usa gmail.users.messages.send() com raw multipart
  8. Se sucesso: Retorna messageId e lista de anexos enviados

Código interno (gmail-executor.service.ts:279-310):

// Add attachments if present
if (data.attachments && data.attachments.length > 0) {
  const boundary = 'boundary_' + Date.now();

  // Convert to multipart email
  const multipartEmail = [
    `MIME-Version: 1.0`,
    `Content-Type: multipart/mixed; boundary="${boundary}"`,
    ...emailLines.slice(0, -2), // Remove body content temporarily
    '',
    `--${boundary}`,
    `Content-Type: text/html; charset=utf-8`,
    '',
    data.html || data.message || '',
    ''
  ];

  // Add each attachment
  for (const attachment of data.attachments) {
    multipartEmail.push(
      `--${boundary}`,
      `Content-Type: ${attachment.contentType || 'application/octet-stream'}`,
      `Content-Disposition: attachment; filename="${attachment.filename || 'attachment'}"`,
      `Content-Transfer-Encoding: base64`,
      '',
      attachment.content || '',
      ''
    );
  }

  multipartEmail.push(`--${boundary}--`);
  return multipartEmail.join('\n');
}

Quando você DEVE usar este Node?

Use SEND_WITH_ATTACHMENT sempre que precisar de envio de emails com arquivos anexados:

Casos de uso

  1. Enviar contratos: "Anexar PDF de contrato assinado após aprovação"
  2. Relatórios mensais: "Enviar planilha Excel com dados do mês"
  3. Comprovantes: "Anexar boleto PDF ou recibo de pagamento"
  4. Documentação: "Enviar manual em PDF junto com email de boas-vindas"

Quando NÃO usar SEND_WITH_ATTACHMENT

  • Emails simples: Use SEND_EMAIL se não houver anexos
  • Arquivos muito grandes: Use links de download (Gmail limita a 25MB)
  • Imagens inline: Para imagens no corpo HTML, use base64 inline
  • Compartilhamento de arquivos: Para muitos arquivos, use Google Drive

Parâmetros Detalhados

attachments (array, obrigatório)

O que é: Array de objetos contendo informações dos arquivos a anexar.

Estrutura de cada anexo:

{
  filename: string,      // Nome do arquivo (ex: "contrato.pdf")
  content: string,       // Conteúdo em base64
  contentType: string    // MIME type (ex: "application/pdf")
}

MIME Types comuns: - PDF: application/pdf - Word: application/vnd.openxmlformats-officedocument.wordprocessingml.document - Excel: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet - PNG: image/png - JPG: image/jpeg - ZIP: application/zip

Flow completo para testar:

{
  "name": "Teste Anexo PDF",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "variable_1",
      "type": "variable",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Preparar PDF",
        "parameters": {
          "variable": "pdfBase64",
          "value": "JVBERi0xLjQKJeLjz9MKMyAwIG9iago8PC9GaWx0ZXIvRmxhdGVEZWNvZGUvTGVuZ3RoIDQ5Pj5zdHJlYW0KeJzLSM3JyVcozy/KSeECAE8kBLEKZW5kc3RyZWFtCmVuZG9iagoKNCAwIG9iago8PC9Db250ZW50cyAzIDAgUi9NZWRpYUJveFswIDAgNTk1IDg0Ml0vUGFyZW50IDIgMCBSL1Jlc291cmNlczw8L0ZvbnQ8PC9GMSA1IDAgUj4+Pj4vVHlwZS9QYWdlPj4KZW5kb2JqCgo1IDAgb2JqCjw8L0Jhc2VGb250L0hlbHZldGljYS9FbmNvZGluZy9XaW5BbnNpRW5jb2RpbmcvU3VidHlwZS9UeXBlMS9UeXBlL0ZvbnQ+PgplbmRvYmoKCjIgMCBvYmoKPDwvS2lkc1s0IDAgUl0vVHlwZS9QYWdlcz4+CmVuZG9iagoKNiAwIG9iago8PC9QYWdlcyAyIDAgUi9UeXBlL0NhdGFsb2c+PgplbmRvYmoKCjcgMCBvYmoKPDwvQ3JlYXRpb25EYXRlKEQ6MjAyNTEwMTIxMjAwMDBaKS9Qcm9kdWNlcihMdW1pbmEgRmxvdyBCdWlsZGVyKT4+CmVuZG9iagoKeHJlZgowIDgKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDE1IDAwMDAwIG4gCjAwMDAwMDAyNzkgMDAwMDAgbiAKMDAwMDAwMDAxOSAwMDAwMCBuIAowMDAwMDAwMTM3IDAwMDAwIG4gCjAwMDAwMDAyMzcgMDAwMDAgbiAKMDAwMDAwMDMyOCAwMDAwMCBuIAowMDAwMDAwMzc3IDAwMDAwIG4gCnRyYWlsZXIKPDwvUm9vdCA2IDAgUi9TaXplIDg+PgpzdGFydHhyZWYKNDcxCiUlRU9G"
        }
      }
    },
    {
      "id": "gmail_1",
      "type": "gmail",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Enviar com Anexo",
        "operation": "send",
        "oauth2": {
          "clientId": "YOUR_CLIENT_ID",
          "clientSecret": "YOUR_SECRET",
          "refreshToken": "YOUR_TOKEN"
        },
        "to": "cliente@example.com",
        "subject": "Documento Anexado",
        "message": "Segue em anexo o documento solicitado.",
        "attachments": [
          {
            "filename": "documento.pdf",
            "content": "{{pdfBase64}}",
            "contentType": "application/pdf"
          }
        ]
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Confirmar",
        "parameters": {
          "message": "Email com PDF anexado enviado!"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 900, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "variable_1" },
    { "source": "variable_1", "target": "gmail_1" },
    { "source": "gmail_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Teste: Execute o flow. Um email com PDF anexado será enviado. O PDF é um documento válido em base64.

Parâmetros

Campo Tipo Obrigatório Descrição
oauth2 object Sim Credenciais OAuth2 (clientId, clientSecret, refreshToken)
operation string Sim Deve ser "send"
to string Sim Email do destinatário
subject string Sim Assunto do email
message string Sim Corpo do email
attachments array Sim Array de anexos (filename, content, contentType)

Exemplo 1: Enviar Contrato PDF

Objetivo: Enviar contrato em PDF após assinatura digital

JSON para Importar

{
  "name": "Enviar Contrato Assinado",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "input_1",
      "type": "input",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Nome Cliente",
        "parameters": {
          "message": "Nome do cliente?",
          "variable": "nomeCliente"
        }
      }
    },
    {
      "id": "email_1",
      "type": "email",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Email Cliente",
        "parameters": {
          "message": "Email do cliente?",
          "variable": "emailCliente"
        }
      }
    },
    {
      "id": "variable_1",
      "type": "variable",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Número Contrato",
        "parameters": {
          "variable": "numeroContrato",
          "value": "{{$random(1000,9999)}}"
        }
      }
    },
    {
      "id": "variable_2",
      "type": "variable",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "PDF Contrato",
        "parameters": {
          "variable": "contratoBase64",
          "value": "JVBERi0xLjQKJeLjz9MKMyAwIG9iago8PC9GaWx0ZXIvRmxhdGVEZWNvZGUvTGVuZ3RoIDQ5Pj5zdHJlYW0KeJzLSM3JyVcozy/KSeECAE8kBLEKZW5kc3RyZWFtCmVuZG9iagoKNCAwIG9iago8PC9Db250ZW50cyAzIDAgUi9NZWRpYUJveFswIDAgNTk1IDg0Ml0vUGFyZW50IDIgMCBSL1Jlc291cmNlczw8L0ZvbnQ8PC9GMSA1IDAgUj4+Pj4vVHlwZS9QYWdlPj4KZW5kb2JqCgo1IDAgb2JqCjw8L0Jhc2VGb250L0hlbHZldGljYS9FbmNvZGluZy9XaW5BbnNpRW5jb2RpbmcvU3VidHlwZS9UeXBlMS9UeXBlL0ZvbnQ+PgplbmRvYmoKCjIgMCBvYmoKPDwvS2lkc1s0IDAgUl0vVHlwZS9QYWdlcz4+CmVuZG9iagoKNiAwIG9iago8PC9QYWdlcyAyIDAgUi9UeXBlL0NhdGFsb2c+PgplbmRvYmoKCjcgMCBvYmoKPDwvQ3JlYXRpb25EYXRlKEQ6MjAyNTEwMTIxMjAwMDBaKS9Qcm9kdWNlcihMdW1pbmEgRmxvdyBCdWlsZGVyKT4+CmVuZG9iagoKeHJlZgowIDgKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDE1IDAwMDAwIG4gCjAwMDAwMDAyNzkgMDAwMDAgbiAKMDAwMDAwMDAxOSAwMDAwMCBuIAowMDAwMDAwMTM3IDAwMDAwIG4gCjAwMDAwMDAyMzcgMDAwMDAgbiAKMDAwMDAwMDMyOCAwMDAwMCBuIAowMDAwMDAwMzc3IDAwMDAwIG4gCnRyYWlsZXIKPDwvUm9vdCA2IDAgUi9TaXplIDg+PgpzdGFydHhyZWYKNDcxCiUlRU9G"
        }
      }
    },
    {
      "id": "gmail_1",
      "type": "gmail",
      "position": { "x": 1100, "y": 100 },
      "data": {
        "label": "Enviar Contrato",
        "operation": "send",
        "oauth2": {
          "clientId": "YOUR_CLIENT_ID",
          "clientSecret": "YOUR_SECRET",
          "refreshToken": "YOUR_TOKEN"
        },
        "to": "{{emailCliente}}",
        "subject": "Contrato #{{numeroContrato}} - Lumina Services",
        "message": "Prezado(a) {{nomeCliente}},\n\nSegue em anexo o contrato #{{numeroContrato}} devidamente assinado.\n\nPor favor, guarde este documento para seus registros.\n\nAtenciosamente,\nEquipe Lumina Services",
        "attachments": [
          {
            "filename": "contrato_{{numeroContrato}}.pdf",
            "content": "{{contratoBase64}}",
            "contentType": "application/pdf"
          }
        ]
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 1300, "y": 100 },
      "data": {
        "label": "Confirmar",
        "parameters": {
          "message": "Contrato #{{numeroContrato}} enviado para {{emailCliente}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1500, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "input_1" },
    { "source": "input_1", "target": "email_1" },
    { "source": "email_1", "target": "variable_1" },
    { "source": "variable_1", "target": "variable_2" },
    { "source": "variable_2", "target": "gmail_1" },
    { "source": "gmail_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Saída esperada:

Sistema: Nome do cliente?
Usuário: João Silva
Sistema: Email do cliente?
Usuário: joao@example.com
Sistema: Contrato #5847 enviado para joao@example.com

Exemplo 2: Múltiplos Anexos

Objetivo: Enviar relatório com múltiplos arquivos (PDF + Excel)

JSON para Importar

{
  "name": "Relatório com Múltiplos Anexos",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "variable_1",
      "type": "variable",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Preparar Anexos",
        "parameters": {
          "variable": "mesReferencia",
          "value": "{{$now('MMMM/YYYY')}}"
        }
      }
    },
    {
      "id": "gmail_1",
      "type": "gmail",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Enviar Relatório",
        "operation": "send",
        "oauth2": {
          "clientId": "YOUR_CLIENT_ID",
          "clientSecret": "YOUR_SECRET",
          "refreshToken": "YOUR_TOKEN"
        },
        "to": "diretoria@empresa.com",
        "subject": "Relatório Mensal - {{mesReferencia}}",
        "message": "Prezada Diretoria,\n\nSegue em anexo o relatório mensal referente a {{mesReferencia}}.\n\nAnexos:\n1. relatorio.pdf - Relatório executivo completo\n2. dados.xlsx - Planilha com dados detalhados\n3. grafico.png - Gráfico de evolução\n\nAtenciosamente,\nSistema de Gestão",
        "attachments": [
          {
            "filename": "relatorio_{{mesReferencia}}.pdf",
            "content": "JVBERi0xLjQKJeLjz9MKMyAwIG9iago8PC9GaWx0ZXIvRmxhdGVEZWNvZGUvTGVuZ3RoIDQ5Pj5zdHJlYW0KeJzLSM3JyVcozy/KSeECAE8kBLEKZW5kc3RyZWFtCmVuZG9iagoKNCAwIG9iago8PC9Db250ZW50cyAzIDAgUi9NZWRpYUJveFswIDAgNTk1IDg0Ml0vUGFyZW50IDIgMCBSL1Jlc291cmNlczw8L0ZvbnQ8PC9GMSA1IDAgUj4+Pj4vVHlwZS9QYWdlPj4KZW5kb2JqCgo1IDAgb2JqCjw8L0Jhc2VGb250L0hlbHZldGljYS9FbmNvZGluZy9XaW5BbnNpRW5jb2RpbmcvU3VidHlwZS9UeXBlMS9UeXBlL0ZvbnQ+PgplbmRvYmoKCjIgMCBvYmoKPDwvS2lkc1s0IDAgUl0vVHlwZS9QYWdlcz4+CmVuZG9iagoKNiAwIG9iago8PC9QYWdlcyAyIDAgUi9UeXBlL0NhdGFsb2c+PgplbmRvYmoKCjcgMCBvYmoKPDwvQ3JlYXRpb25EYXRlKEQ6MjAyNTEwMTIxMjAwMDBaKS9Qcm9kdWNlcihMdW1pbmEgRmxvdyBCdWlsZGVyKT4+CmVuZG9iagoKeHJlZgowIDgKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDE1IDAwMDAwIG4gCjAwMDAwMDAyNzkgMDAwMDAgbiAKMDAwMDAwMDAxOSAwMDAwMCBuIAowMDAwMDAwMTM3IDAwMDAwIG4gCjAwMDAwMDAyMzcgMDAwMDAgbiAKMDAwMDAwMDMyOCAwMDAwMCBuIAowMDAwMDAwMzc3IDAwMDAwIG4gCnRyYWlsZXIKPDwvUm9vdCA2IDAgUi9TaXplIDg+PgpzdGFydHhyZWYKNDcxCiUlRU9G",
            "contentType": "application/pdf"
          },
          {
            "filename": "dados.xlsx",
            "content": "UEsDBBQAAAAIAAAAAAAAAAAAAAAAAAAAAAAKAAAAX3JlbHMvLnJlbHON0L0KwjAUBeC7kHcIudsCTbWGjk3F1JEWOmQybRtIm0uTbH17BwfBxbH3nO8eOO6L7V4CyKKVqwg02g82LrYs85AW9QqVPBBLjIUMg7WZZK3BplvABLkCIoB3FShBFqEJLW01CkV5A+YqCZpXpUwQBGKqC8BKQ3Zm8y2EWgAR9n8kFdFX6ckFJPETVrBMQKHEJZAYkDWBKRHaJBPxH6sQwAAAAP//AwBQSwMEFAAGAAgAAAAhALh5sJxSAgAAtQUAABMAAABbQ29udGVudF9UeXBlc10ueG1spJXPTsMwDMbvSLxD1H2tdgzRqm7dxAQH4MYL5CRu6y1JlDjsePW4/WkZSFx2S+z8+XyJ7fHi+qOu0J6s0o1YwFkWQsS5NIzEi8W//vwxnN5cXoQz3HA+wNe6wRSu4PYewAx//Xxlz6rNSHWCDp7+JrHe4hIeQrjnsUxdZnGf57d5kSaUXZ4VNE1iiSRleTKJeRKfl+dFMolZXrKMDHp+FNOqazhvgYfbDg8w=",
            "contentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
          },
          {
            "filename": "grafico.png",
            "content": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
            "contentType": "image/png"
          }
        ]
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Confirmar",
        "parameters": {
          "message": "Relatório {{mesReferencia}} enviado com 3 anexos"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 900, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "variable_1" },
    { "source": "variable_1", "target": "gmail_1" },
    { "source": "gmail_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Saída esperada:

Sistema: Relatório Outubro/2025 enviado com 3 anexos

Resposta do Node

{
  "success": true,
  "action": "send_with_attachment",
  "messageId": "18f2b9c4d5e6a7f8",
  "threadId": "18f2b9c4d5e6a7f8",
  "labelIds": ["SENT"],
  "attachmentsCount": 2,
  "timestamp": "2025-10-12T21:30:00.000Z"
}

Boas Práticas

SIM:

  • Limite anexos a 25MB total (limite do Gmail)
  • Use nomes descritivos para arquivos
  • Sempre especifique contentType correto
  • Comprima arquivos grandes antes de anexar
  • Mencione anexos no corpo do email

NÃO:

  • Não anexe executáveis (.exe, .bat) - Gmail bloqueia
  • Não exceda limite de 25MB
  • Não use caracteres especiais em nomes de arquivo
  • Não anexe arquivos sem content (string vazia)

Dicas

💡 Dica 1: Para arquivos muito grandes, use Google Drive e envie apenas o link

💡 Dica 2: Converta arquivos para base64 usando Node.js Buffer ou ferramenta online

💡 Dica 3: Comprima múltiplos arquivos em ZIP antes de anexar

💡 Dica 4: Use nomes de arquivo dinâmicos com variáveis para organização

💡 Dica 5: Armazene templates de anexos em variáveis para reutilização

Próximo Node

SEND_EMAIL - Enviar sem anexos → SEND_HTML_EMAIL - Enviar HTML sem anexos → GET_EMAILS - Recuperar emails com anexos