AI Automation & Agents  

Save Gmail Attachments to Google Drive Automatically

Abstract / Overview

This tutorial shows how to auto-save Gmail attachments into Google Drive with Make.com. The scenario watches mail, filters for files worth keeping, routes each attachment into the correct Drive folder, renames consistently, labels the message, and logs the action. The result: organized documents without manual download or drag-and-drop.

Assumption: You can connect Gmail and Google Drive to Make.com. Make functions such as formatDate, replaceAll, lower, if, and simple Routers and Filters are available.

ChatGPT Image Sep 5, 2025, 10_03_19 AM

Conceptual Background

  • Event source. Gmail holds messages with attachments. A search query or instant watch selects candidates.

  • Unit of work. One attachment is one save operation. Iterate attachments per message.

  • Routing. Destination folder depends on message metadata: labels, sender, subject keywords, or attachment MIME type.

  • Idempotency. A de-dup key prevents saving the same file twice. Combine Gmail messageId, attachmentId, and size.

  • Naming. Normalize filenames for searchability: YYYY-MM-DD_sender_subject_original.ext.

  • Observability. Write a log row (Sheets or Drive file properties) with source message link, saved file ID, and timestamp.

  • Safety. Skip inline images and tiny spacer files. Respect virus flags and Drive quotas. Use least-privilege sharing.

Step-by-Step Walkthrough

1) Plan your folder scheme

Create a root folder in Drive: Mail Attachments. Add subfolders that match your workflow. Pick one of these patterns:

  • By business functionInvoices/, Contracts/, HR/, Receipts/.

  • By senderVendors/Acme/, Vendors/Globex/.

  • By timeYYYY/MM/ under each function (e.g., Invoices/2025/08/).

Record your root FOLDER_ID. You may optionally maintain a Google Sheet mapping senders or labels to subfolders:

RuleType,RuleValue,Subfolder
label,finance/invoices,Invoices
from,[email protected],Vendors/Acme
mime,application/pdf,PDF

2) Prepare Gmail labels and filters (optional but recommended)

  • Create labels like finance/invoices, legal/contracts, saved-to-drive.

  • Add Gmail filters to auto-label incoming messages:

    • Invoices: from:(billing OR invoices) has:attachment

    • Receipts: subject:(receipt OR invoice) has:attachment

  • These labels simplify routing and reduce false positives.

3) Build the Make.com scenario

Create one scenario with a Router. Core modules:

  • Trigger

    • Option A (interval): Gmail → Search messages. Query examples below. Runs every 5–15 minutes.

    • Option B (instant): Gmail → Watch emails. Make processes for each new message as it arrives.

  • Iterator

    • Gmail → Get attachments or iterate over the attachments[] array from the trigger bundle.

  • Filters

    • Skip inline images (isInline = true or filename matches image00\d+\.(png|jpg)), and files < 3 KB.

  • Routing logic

    • Map to subfolders by label, sender, subject, or MIME type. Use a Router with prioritized paths.

  • Ensure folder

    • Drive → Search files/folders for the target path. If not found, Drive → Create a folder.

  • Upload

    • Drive → Upload an attachment using the attachment bytes and chosen folder.

  • Post-processing

    • Gmail → Add labelssaved-to-drive. Optional: Archive or Remove label.

  • Log

    • Google Sheets → Add a row with messageId, attachmentId, filename, folder, fileId, savedAt.

  • De-dup

    • Make Data Store: key = {{messageId}}::{{attachmentId}}. If it exists, skip.

4) Construct a robust Gmail search

Use one tight query for polling runs:

  • Save everything with any attachment:
    in:inbox has:attachment -in:trash -category:promotions

  • Narrow by type and recency:
    has:attachment filename:(pdf OR docx) newer_than:1d -in:trash

  • Use labels you applied with Gmail filters:
    label:finance/invoices has:attachment -label:saved-to-drive

5) Standardize filenames

Create a consistent name before upload:

{{ 
  concat(
    formatDate(date; "YYYY-MM-DD");
    "_";
    lower(replaceAll(from; "[^a-zA-Z0-9]+"; "-"));
    "_";
    lower(replaceAll(substring(subject; 0; 60); "[^a-zA-Z0-9]+"; "-"));
    "_";
    lower(replaceAll(attachmentName; "[^a-zA-Z0-9\\.\\-]+"; "-"))
  )
}}
  • date = message internal date.

  • from = sender email or display name.

  • subject truncated to 60 chars.

  • attachmentName sanitized.

6) Build dynamic folder selection

Order of precedence (use a Router with first-match-wins):

  • If label contains finance/invoicesMail Attachments/Invoices/YYYY/MM

  • Else if from ends with @acme.comMail Attachments/Vendors/Acme/YYYY/MM

  • Else if mime starts with image/Mail Attachments/Images/YYYY/MM

  • Else → Mail Attachments/Misc/YYYY/MM

Compute year and month once:

year = {{ formatDate(date; "YYYY") }}
month = {{ formatDate(date; "MM") }}
path = {{ concat(baseFolder; "/"; subfolder; "/"; year; "/"; month) }}

7) Test and verify

  • Send an email to yourself with one PDF and one inline PNG. Only the PDF should be saved.

  • Confirm the file in Drive, the Gmail message labeled saved-to-drive, and the log row with the file ID.

  • Send from a vendor on your mapping sheet to ensure correct routing.

8) Harden for production

  • Add an Error handler branch on Drive upload with backoff and retry.

  • Limit maximum attachment size (e.g., skip > 25–50 MB if your plan cannot handle big files).

  • Cache folder IDs in a Data Store (path → folderId) to avoid repeated Drive searches.

  • Add a Virus flag check: if Drive reports a virus scan warning on download, route to a quarantine folder.

Code / JSON Snippets

A) Gmail query recipes (paste into Make)

# General
in:inbox has:attachment -in:trash -category:promotions

# PDF or DOCX only, last day
has:attachment filename:(pdf OR docx) newer_than:1d -in:trash

# Label-driven capture
label:finance/invoices has:attachment -label:saved-to-drive

B) Filename sanitizer (Make “Set multiple variables”)

safeSubject = {{ lower(replaceAll(substring(subject; 0; 60); "[^a-zA-Z0-9]+"; "-")) }}
safeFrom = {{ lower(replaceAll(from; "[^a-zA-Z0-9]+"; "-")) }}
safeAttach = {{ lower(replaceAll(attachmentName; "[^a-zA-Z0-9\\.\\-]+"; "-")) }}
outName = {{ concat(formatDate(date; "YYYY-MM-DD"); "_"; safeFrom; "_"; safeSubject; "_"; safeAttach) }}

C) Destination path builder

year = {{ formatDate(date; "YYYY") }}
month = {{ formatDate(date; "MM") }}
subfolder = {{ 
  if(contains(labels; "finance/invoices"); "Invoices";
    if( endsWith(lower(from); "@acme.com"); "Vendors/Acme";
      if( startsWith(mimeType; "image/"); "Images"; "Misc")
    )
  )
}}
path = {{ concat("Mail Attachments/"; subfolder; "/"; year; "/"; month) }}

D) Sample workflow JSON code (Make scenario blueprint)

Import shape varies by account. Replace placeholders.

{
  "name": "Gmail → Drive Attachment Saver",
  "version": 3,
  "schedule": { "type": "interval", "interval": 10 },
  "modules": [
    {
      "id": "1",
      "name": "Search messages",
      "type": "gmail",
      "func": "searchMessages",
      "params": {
        "connectionId": "conn_gmail_1",
        "query": "has:attachment -in:trash -label:saved-to-drive newer_than:1d",
        "limit": 20
      }
    },
    {
      "id": "2",
      "name": "Get attachments",
      "type": "gmail",
      "func": "getAttachments",
      "params": { "connectionId": "conn_gmail_1", "messageId": "{{1.id}}" }
    },
    {
      "id": "3",
      "name": "Iterator: each attachment",
      "type": "iterator",
      "func": "each",
      "params": { "array": "{{2.attachments}}" }
    },
    {
      "id": "4",
      "name": "Filter: skip inline/tiny",
      "type": "flow",
      "func": "filter",
      "params": {
        "condition": "{{ (not isInline) and (size > 3000) }}"
      }
    },
    {
      "id": "5",
      "name": "De-dup check",
      "type": "datastore",
      "func": "get",
      "params": { "store": "GmailDriveDedup", "key": "{{1.id}}::{{3.attachmentId}}" }
    },
    {
      "id": "6",
      "name": "Filter: unseen only",
      "type": "flow",
      "func": "filter",
      "params": { "condition": "{{ empty(5.value) }}" }
    },
    {
      "id": "7",
      "name": "Set variables",
      "type": "tools",
      "func": "setVars",
      "params": {
        "vars": {
          "date": "{{1.internalDate}}",
          "from": "{{1.from}}",
          "subject": "{{1.subject}}",
          "mimeType": "{{3.mimeType}}",
          "attachmentName": "{{3.filename}}",
          "year": "{{ formatDate(1.internalDate; \"YYYY\") }}",
          "month": "{{ formatDate(1.internalDate; \"MM\") }}",
          "subfolder": "{{ if(contains(1.labels; \"finance/invoices\"); \"Invoices\"; if(endsWith(lower(1.from); \"@acme.com\"); \"Vendors/Acme\"; if(startsWith(3.mimeType; \"image/\"); \"Images\"; \"Misc\"))) }}",
          "outName": "{{ concat(formatDate(1.internalDate; \"YYYY-MM-DD\"); \"_\"; lower(replaceAll(1.from; \"[^a-zA-Z0-9]+\"; \"-\")); \"_\"; lower(replaceAll(substring(1.subject; 0; 60); \"[^a-zA-Z0-9]+\"; \"-\")); \"_\"; lower(replaceAll(3.filename; \"[^a-zA-Z0-9\\.\\-]+\"; \"-\"))) }}",
          "path": "{{ concat(\"Mail Attachments/\"; subfolder; \"/\"; year; \"/\"; month) }}"
        }
      }
    },
    {
      "id": "8",
      "name": "Find or create folder",
      "type": "google-drive",
      "func": "ensureFolder",
      "params": {
        "connectionId": "conn_drive_1",
        "rootFolderId": "YOUR_ROOT_FOLDER_ID",
        "path": "{{path}}"
      }
    },
    {
      "id": "9",
      "name": "Upload attachment",
      "type": "google-drive",
      "func": "uploadFile",
      "params": {
        "connectionId": "conn_drive_1",
        "folderId": "{{8.folderId}}",
        "fileName": "{{outName}}",
        "data": "{{3.data}}",
        "mimeType": "{{3.mimeType}}"
      }
    },
    {
      "id": "10",
      "name": "Mark de-dup",
      "type": "datastore",
      "func": "set",
      "params": {
        "store": "GmailDriveDedup",
        "key": "{{1.id}}::{{3.attachmentId}}",
        "value": "{ \"fileId\": \"{{9.fileId}}\", \"savedAt\": \"{{ formatDate(now; \\\"YYYY-MM-DDTHH:mm:ssZ\\\"; \\\"UTC\\\") }}\" }",
        "ttl": 7776000
      }
    },
    {
      "id": "11",
      "name": "Label message",
      "type": "gmail",
      "func": "modifyMessage",
      "params": {
        "connectionId": "conn_gmail_1",
        "messageId": "{{1.id}}",
        "addLabels": ["saved-to-drive"]
      }
    },
    {
      "id": "12",
      "name": "Log row",
      "type": "google-sheets",
      "func": "appendRow",
      "params": {
        "connectionId": "conn_sheets_1",
        "spreadsheetId": "YOUR_SHEET_ID",
        "sheetName": "Attachment Log",
        "values": ["{{1.id}}","{{3.attachmentId}}","{{9.fileId}}","{{8.path}}","{{7.outName}}","{{ formatDate(now; \"YYYY-MM-DD HH:mm:ss\") }}"]
      }
    }
  ],
  "links": [
    {"from_module":"1","to_module":"2"},
    {"from_module":"2","to_module":"3"},
    {"from_module":"3","to_module":"4"},
    {"from_module":"4","to_module":"5"},
    {"from_module":"5","to_module":"6"},
    {"from_module":"6","to_module":"7"},
    {"from_module":"7","to_module":"8"},
    {"from_module":"8","to_module":"9"},
    {"from_module":"9","to_module":"10"},
    {"from_module":"10","to_module":"11"},
    {"from_module":"11","to_module":"12"}
  ]
}

E) Optional sender/label routing table (Google Sheets CSV seed)

RuleType,RuleValue,Subfolder
label,finance/invoices,Invoices
label,legal/contracts,Contracts
from,[email protected],Vendors/Acme
from,[email protected],Vendors/Globex
mime,image/,Images

F) Simple HTTP fallback (advanced)

If a specific Drive feature is missing in your Make app version, you can post via HTTP:

POST https://www.googleapis.com/upload/drive/v3/files?uploadType=media
Authorization: Bearer YOUR_OAUTH_TOKEN
Content-Type: application/pdf

{{attachment_binary}}

Then call PATCH /drive/v3/files/{fileId} to set name and parents. Prefer native modules whenever possible.

Use Cases / Scenarios

  • Finance operations. Route invoices, receipts, and payment confirmations into a month-organized ledger.

  • Legal and compliance. Archive signed agreements and notices by counterparty.

  • HR onboarding. Store ID scans and forms by candidate email.

  • Project delivery. Collect statements of work and acceptance forms under each client folder.

  • Education. File assignment submissions and certificates by cohort and date.

Limitations / Considerations

  • Inline images. Many emails embed logos as attachments. Filter by isInline or filename patterns to avoid clutter.

  • Attachment size. Very large files may exceed limits or time out. Add a size guard and route oversize files to a review queue.

  • Drive quotas and permissions. Respect storage and rate limits. Keep the automation account in a Shared Drive to avoid orphaned files.

  • Latency. Gmail indexing can add small delays for search-based triggers. Instant watch minimizes delay but still depends on provider events.

  • Security. Do not auto-share saved files. Keep the default private access. Never enable “Anyone with link” by default.

  • PII. Filenames may include personal data. Use initials or hashed identifiers if required by policy.

Budget and throughput (free-tier planning)

Let:

  • E = emails processed per day,

  • A = average attachments per processed email,

  • Base ops per email ≈ 1 (search/watch) + 1 (get attachments),

  • Ops per attachment ≈ 1 (upload) + 1 (log) + 1 (dedup write) ≈ 3.
    Daily operations ≈ 2E + 3(E × A).
    Example: E=40, A=1.5 → ops/day ≈ 80 + 180 = 260. Reduce by tightening the Gmail query and batching.

Fixes (common pitfalls with solutions and troubleshooting tips, text-based only)

  • Duplicate saves. Use a Data Store key messageId::attachmentId. Set it only after a successful upload.

  • Inline logos were saved by mistake. Filter isInline = true, ignore filenames like image001, or require size > 3 KB.

  • Broken filenames or illegal characters. Sanitize with replaceAll("[^a-zA-Z0-9\\.\\-]"; "-"). Trim to a safe length.

  • Wrong folder. Make routing precedence explicit. Put label-based routing before sender-based routing.

  • Drive “File not found” on subsequent steps. Always pass the returned fileId from the upload step forward. Avoid name-based lookups right after creation.

  • Time zone mismatch in dates. Use formatDate(internalDate; "YYYY-MM-DD"; "Your/IANA") to match your reporting region.

  • Rate-limit errors. Add an Error handler with exponential backoff and a short Sleep between uploads in bursts.

  • Corrupted downloads. Ensure you pass the binary attachment, not a base64 string. Use the Gmail module’s binary output.

Diagram

File-Automation

Conclusion

Gmail provides structured access and search. Make orchestrates selection, filtering, renaming, and routing. Google Drive stores the artifacts in predictable folders with clean names. With idempotency, logs, and safe defaults, the system organizes documents without lifting a finger and scales from personal filing to light team operations.