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 function → Invoices/
, Contracts/
, HR/
, Receipts/
.
By sender → Vendors/Acme/
, Vendors/Globex/
.
By time → YYYY/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:
These labels simplify routing and reduce false positives.
3) Build the Make.com scenario
Create one scenario with a Router. Core modules:
Trigger
Iterator
Filters
Routing logic
Ensure folder
Upload
Post-processing
Log
Google Sheets → Add a row with messageId
, attachmentId
, filename
, folder
, fileId
, savedAt
.
De-dup
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/invoices
→ Mail Attachments/Invoices/YYYY/MM
Else if from
ends with @acme.com
→ Mail 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.