Source: https://cli.nylas.com/guides/email-to-google-drive

# Save Email Attachments to Google Drive

Invoices, signed PDFs, and receipts land in your inbox and need to live in Google Drive. Most teams reach for a no-code connector and hit per-file fees plus monthly run caps. The Nylas CLI lists each message's attachments as JSON and downloads them locally; the Google Drive API stores each one in two short requests. This guide builds an email-to-Drive pipeline you control, filing every attachment into the folder you choose on a schedule.

Written by [Prem Keshari](https://cli.nylas.com/authors/prem-keshari) Senior SRE

Reviewed by [Qasim Muhammad](https://cli.nylas.com/authors/qasim-muhammad)

Updated June 9, 2026

> **TL;DR:** List a message's files with `nylas email attachments list`, download each with `nylas email attachments download`, then create the file with the Google Drive `files.create` API and upload its bytes. The CLI handles the inbox across six providers; Drive handles the storage. By the last section you'll run the whole thing on a cron, filing yesterday's attachments into a named folder with no per-file fee.

Command references used in this guide: [`nylas email search`](https://cli.nylas.com/docs/commands/email-search), [`nylas email list`](https://cli.nylas.com/docs/commands/email-list), and the [attachments subcommands](https://cli.nylas.com/docs/commands).

## How do I find email messages that have attachments?

Find messages with files by running `nylas email search` with the `--has-attachment` flag, which returns only messages that carry at least one attachment. Add `--json` to get structured output you can pipe into a loop, and scope the window with `--after` so you process a known date range instead of the whole mailbox. Each result includes the message ID you need next.

The search runs against whichever grant the CLI has configured, so it works the same for Gmail, Outlook, and the four other supported providers without provider-specific code. A practical filter is sender plus date: most attachment workflows file documents from one source, such as a billing system or a scanning service. The `--limit` flag defaults to 20 and auto-paginates past 200, so a daily run with `--limit 200` covers a busy inbox. Capturing the message IDs to a file keeps the download step idempotent on reruns.

```bash
# Find messages with attachments from the last day, as JSON
nylas email search "*" --has-attachment \
  --from "billing@vendor.com" --after 2026-06-08 \
  --json --limit 200 > messages.json

# Keep just the message IDs for the next step
jq -r '.[].id' messages.json > message-ids.txt
```

## How do I list and download each attachment?

List a message's files with `nylas email attachments list <message-id>`, which returns each attachment's ID, filename, content type, and size. Then download each one with `nylas email attachments download <attachment-id> <message-id>`. The `-o` flag sets the output path; without it the tool writes to the original filename. Looping over the IDs from the search step pulls every file to local disk.

Adding `--json` to the list command gives you the filename and content type per attachment, which the Drive upload needs for its metadata and MIME boundary. A 25 MB attachment is the Gmail per-message ceiling, so most files download in under a second; the size field lets you skip anything over a threshold before spending bandwidth. Sanitize filenames before writing them to disk, since a crafted `name` field could contain path separators. Write each file into a per-message directory so two messages with the same filename never collide.

```bash
mkdir -p downloads
while read -r MSG_ID; do
  # List attachments for this message as JSON
  nylas email attachments list "$MSG_ID" --json > atts.json

  jq -c '.[]' atts.json | while read -r att; do
    ATT_ID=$(echo "$att" | jq -r '.id')
    NAME=$(echo "$att"   | jq -r '.filename // "file.bin"' | tr -d '/')
    nylas email attachments download "$ATT_ID" "$MSG_ID" \
      -o "downloads/${MSG_ID}-${NAME}"
  done
done < message-ids.txt
```

## How do I upload a file to the Google Drive API?

Upload in two requests, the pattern curl handles cleanly: POST the file's metadata to `https://www.googleapis.com/drive/v3/files` to create the entry, then PATCH its bytes to `https://www.googleapis.com/upload/drive/v3/files/<id>?uploadType=media`. (curl's `-F` sends `multipart/form-data`, which Drive's multipart endpoint rejects, so the two-step media upload is the reliable shell path.) Both calls are documented in Google's [Manage uploads](https://developers.google.com/workspace/drive/api/guides/manage-uploads) guide.

Simple media upload handles everyday attachments; Google recommends resumable uploads above 5 MB, and the same `files.create` method covers both per the [files.create reference](https://developers.google.com/workspace/drive/api/reference/rest/v3/files/create). You authenticate with an OAuth 2.0 access token carrying the `drive.file` scope, which limits the app to files it creates — see the [Drive auth guide](https://developers.google.com/workspace/drive/api/guides/about-auth). Set `parents` to a folder ID to file everything in one place, and the token lives separately from the mailbox grant the tool manages.

```bash
FOLDER_ID="your-drive-folder-id"
upload_to_drive() {
  local path="$1" name="$2"
  # 1. Create the file's metadata (jq builds the JSON), capture the new ID
  local meta id
  meta=$(jq -n --arg n "$name" --arg p "$FOLDER_ID" '{name: $n, parents: [$p]}')
  id=$(curl -s -X POST "https://www.googleapis.com/drive/v3/files" \
    -H "Authorization: Bearer $GOOGLE_TOKEN" \
    -H "Content-Type: application/json" \
    -d "$meta" | jq -r '.id')
  # 2. Upload the bytes to that file (simple media upload)
  curl -s -X PATCH \
    "https://www.googleapis.com/upload/drive/v3/files/$id?uploadType=media" \
    -H "Authorization: Bearer $GOOGLE_TOKEN" \
    --data-binary "@$path"
}

for f in downloads/*; do
  upload_to_drive "$f" "$(basename "$f")"
done
```

## What message metadata should I attach to each upload?

Attach the sender, subject, and received date so a filed document stays traceable to its source email. Drive supports an `appProperties` object — up to 30 custom key-value pairs per file — for exactly this kind of private metadata. Pull the fields from the message JSON you already saved, then merge them into the upload's metadata part alongside the name and parent folder.

Custom `appProperties` keys and values are limited to 124 bytes combined per pair, so store IDs and short strings rather than full email bodies. Tagging the originating message ID lets a rerun query Drive and skip files it already uploaded, which is the de-duplication hook for a scheduled job. You can also prefix the filename with the ISO date so the folder sorts chronologically without opening each file. Keeping provenance in `appProperties` means an auditor can answer “which email did this PDF come from?” months later from the file's own record.

```bash
# Build a metadata part that carries email provenance
build_meta() {
  local name="$1" msg_id="$2" sender="$3"
  jq -nc \
    --arg name "$name" --arg folder "$FOLDER_ID" \
    --arg msg "$msg_id" --arg from "$sender" \
    '{name:$name, parents:[$folder],
      appProperties:{sourceMessageId:$msg, sender:$from}}'
}
```

## Why run the pipeline on a schedule instead of by hand?

Run it on a schedule because attachments arrive continuously and a manual export misses anything received between runs. A daily cron that searches `--after` yesterday, downloads, and uploads keeps Drive current with zero clicks. Scoping each run to a one-day window means you process a small, predictable batch instead of re-scanning the entire mailbox every night.

For near-real-time filing, drive the same download-and-upload step from a `message.created` webhook so a new attachment lands in Drive within seconds of arriving. The processing code is identical; only the trigger changes. Store each uploaded file's `sourceMessageId` in `appProperties` and query Drive before uploading, so a retried run after a network failure never creates duplicates. Cron handles the common case in three lines of crontab; the webhook path is worth the extra setup only when minutes matter, such as a finance team that reconciles invoices the moment they land.

```bash
# crontab -e — file yesterday's attachments every morning at 6am
0 6 * * * /usr/local/bin/email-to-drive.sh >> ~/drive-sync.log 2>&1
```

## Next steps

- [Save Email Attachments to OneDrive](https://cli.nylas.com/guides/email-to-onedrive) — List and download email attachments with the Nylas CLI, then PUT…
- [Parse email attachments](https://cli.nylas.com/guides/parse-email-attachments) — list, filter, and extract files before uploading
- [Sync email to S3](https://cli.nylas.com/guides/sync-email-to-s3) — the same pattern into object storage
- [Save email attachments to Dropbox](https://cli.nylas.com/guides/email-to-dropbox) — file attachments into Dropbox instead
- [Send email to a Notion database](https://cli.nylas.com/guides/email-to-notion) — file the message body, not just files
- [Save email as PDF](https://cli.nylas.com/guides/email-to-pdf-cli) — archive the message itself as a document
- [Full command reference](https://cli.nylas.com/docs/commands) — every flag and subcommand documented
- [Google Drive: Manage uploads](https://developers.google.com/workspace/drive/api/guides/manage-uploads) — multipart and resumable upload contract
- [Drive API files.create reference](https://developers.google.com/workspace/drive/api/reference/rest/v3/files/create) — request shape and parameters
- [Drive API auth guide](https://developers.google.com/workspace/drive/api/guides/about-auth) — the drive.file OAuth scope
