Attaching Google Drive files to Gmail draft

I’m new to n8n and struggling with my first workflow. I’m trying to grab some files from Google Drive and add them as attachments to a gmail draft.

I’ve got the files files downloaed, and they show correctly in the binary tab of the Google Drive node. The problem comes with attaching them.

Firstly, the workflow makes a separate email for each file, I assume because it’s looping through each of the binary objects. How do I attach multiple files to a single email? I’d like to add an attachment from another source, too.

Secondly, the attachments are showing up without any name or mime type, just as binary blobs. How can I preserve their file types and names from Google Drive?

Welcome to the community @matthewbellringer

Firstly, the workflow makes a separate email for each file, I assume because it’s looping through each of the binary objects. How do I attach multiple files to a single email? I’d like to add an attachment from another source, too.

Yeah, it’s looping the items. Are the numbers of files that you have to download always?

Secondly, the attachments are showing up without any name or mime type, just as binary blobs. How can I preserve their file types and names from Google Drive?

I just tested it, and yeah, it seems to be an issue. Gonna check it out.

Thanks, good to be here!

It’ll need to take a variable number of files - basically, it’s “all files in this folder”. Plus a different file generated from another node.

I’m thinking the approach is to combine them all in a single array object, and then split that object out again in the expression which lists attachments. But as I’m new to this I could be on completely the wrong track!

Super, thanks for looking into that one.

Ok, in that case, you need to use a function node to merge all the binary data into one record.

The function node should look as follow:

const results = { json: {}, binary: {} }

for (const [index, item] of items.entries()) {
  for (const [keyIndex, key] of Object.keys(item.binary).entries()) {
    results.binary[`data_${index}_${keyIndex}`] = item.binary[key]
  }
}

return [results];

You can also check the example workflow below:

{
  "nodes": [
    {
      "parameters": {},
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [
        330,
        300
      ]
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "operation": "list",
        "limit": 2,
        "queryFilters": {
          "mimeType": [
            {
              "mimeType": "custom",
              "customMimeType": "application/pdf"
            }
          ],
          "name": []
        },
        "options": {
          "fields": [
            "*"
          ]
        }
      },
      "name": "Google Drive",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 1,
      "position": [
        640,
        300
      ],
      "credentials": {
        "googleDriveOAuth2Api": "asasasas"
      }
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "operation": "download",
        "fileId": "={{$node[\"Google Drive\"].json[\"id\"]}}"
      },
      "name": "Google Drive1",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 1,
      "position": [
        910,
        300
      ],
      "credentials": {
        "googleDriveOAuth2Api": "asasasas"
      }
    },
    {
      "parameters": {
        "functionCode": "const results = { json: {}, binary: {} }\n\nfor (const [index, item] of items.entries()) {\n  for (const [keyIndex, key] of Object.keys(item.binary).entries()) {\n    results.binary[`data_${index}_${keyIndex}`] = item.binary[key]\n  }\n}\n\nreturn [results];"
      },
      "name": "Function",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1140,
        300
      ]
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "Google Drive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Drive": {
      "main": [
        [
          {
            "node": "Google Drive1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Drive1": {
      "main": [
        [
          {
            "node": "Function",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Secondly, the attachments are showing up without any name or mime type, just as binary blobs. How can I preserve their file types and names from Google Drive?

Ok, I checked this. It’s not a bug. Sadly the Google Drive API does not return the name of the file when you download it.

To fix this, we have two options:

  1. Make a second request with the File ID to retrieve just the name of the file. (Kind of inefficient)

  2. Add an extra field called File Name, and use that as the name of the file.

I’m sorry, you’ve lost me. It might be because I don’t understand how the connectors work as loops and how data is passed between nodes properly?

I’ve created a function with your code and connected it to a working node that generates a file. Then, I connect the function on to the gmail that creates the draft. The attachment field on that node is “data”. When I run it I get the error “cannot read property ‘name’ of undefined”.

That’s an annoying Google feature!

Sorry, lost me again. Where I am adding the File Name field? It’s not there in the Download node.

We have two options to solve the issue. It needs to be implemented.

Yes, the default value is data. If you use the function node I sent you, you need to use data_${i}_${y} where i and y are index. Now, I just realized that since the number of attachments is dynamic, the current implementation of the Gmail node will not work. I have to make a small change to make it work. I will do it today and keep you posted.

Ok, the two PR below should enable your use case. We will let you know when they are released.

That’s brilliant, thank you!

Both got released with [email protected]