Guide

Save Email Attachments to Dropbox

Invoices, signed contracts, and vendor PDFs arrive as email attachments and need to land in a shared Dropbox folder. The manual route is download, switch tabs, drag, repeat. The CLI lists and downloads each attachment as a real file; the Dropbox API uploads it with one POST and a bearer token. This guide builds an email-to-Dropbox pipeline you can run on a cron, mapping sender and date into the destination path so files stay organized without a paid connector.

Written by Qasim Muhammad Staff SRE

VerifiedCLI 3.1.17 · Gmail, Outlook · last tested June 9, 2026

Command references used in this guide: nylas email search, nylas email list, and nylas email attachments.

How do you save an email attachment to Dropbox?

You save an email attachment to Dropbox in two stages: pull the file to disk with the CLI, then POST it to the Dropbox API. nylas email attachments download writes the binary to a local path, and a single call to the /2/files/upload endpoint with an OAuth bearer token stores it in your account. The upload contract is documented in the Dropbox HTTP API reference.

The split matters because the two sides authenticate separately. The mailbox grant lives in the CLI; the Dropbox token is a generated app credential. Dropbox's /2/files/upload accepts files up to 150 MB in a single request, so most invoices, contracts, and scanned PDFs go through in one POST. Larger files need the upload-session endpoints, which this pipeline skips.

# Find messages that actually carry files, newest first
nylas email search "*" --has-attachment --after 2026-06-01 --json --limit 50 > messages.json

How do you download attachments with the CLI?

You download attachments by reading each message's attachment IDs, then calling nylas email attachments download <attachment-id> <message-id> once per file. The list subcommand returns the parts of a message — filename, size, content type, and ID — and download writes the bytes to a path you choose with -o. A 2 MB PDF downloads in well under a second on a normal connection.

Run nylas email attachments list <message-id> --json first so you can filter parts by content type. Inline images and signatures often ride along as attachments, so skip anything that isn't the document you want. The CLI works the same across Gmail, Outlook, and four other providers, so the loop below never branches on provider.

mkdir -p downloads
jq -r '.[].id' messages.json | while read -r msg_id; do
  nylas email attachments list "$msg_id" --json | \
    jq -r '.[] | select(.content_type|test("pdf|image")) | .id' | \
    while read -r att_id; do
      nylas email attachments download "$att_id" "$msg_id" -o "downloads/$att_id"
    done
done

How do you upload a file to the Dropbox API?

You upload a file by POSTing its raw bytes to https://content.dropboxapi.com/2/files/upload with two headers: an Authorization: Bearer token and a Dropbox-API-Arg JSON header that names the destination path. Dropbox separates content endpoints (binary upload) from RPC endpoints (metadata), which is why the upload host is content.dropboxapi.com, not api.dropboxapi.com.

The header gotcha: Dropbox-API-Arg must be HTTP-header-safe JSON, and the request body is the file itself with Content-Type: application/octet-stream. Set mode to add with autorename so a duplicate filename gets a numeric suffix instead of failing. Generate the token from a scoped app in the Dropbox data ingress guide.

DROPBOX_TOKEN="sl.your-generated-token"
for f in downloads/*; do
  name=$(basename "$f")
  curl -s -X POST https://content.dropboxapi.com/2/files/upload \
    -H "Authorization: Bearer $DROPBOX_TOKEN" \
    -H "Dropbox-API-Arg: {\"path\":\"/Email Attachments/$name\",\"mode\":\"add\",\"autorename\":true}" \
    -H "Content-Type: application/octet-stream" \
    --data-binary @"$f"
done

How do you organize files by sender or date?

You organize uploads by building the Dropbox path from message metadata instead of dumping every file into one folder. The CLI's JSON carries the sender email and a date field as a Unix timestamp, so you can route a vendor's invoices into /Invoices/acme.com/2026-06/ automatically. Dropbox creates any missing folders in the path on upload, so no pre-creation step is needed.

Derive the folder from the sender domain and the message month, then pass the full path in the Dropbox-API-Arg header. This keeps a year of attachments navigable: roughly 500 files spread across per-sender folders instead of one flat directory. Sanitize the filename to strip slashes, since a stray / in an original filename would create an unintended subfolder.

jq -c '.[]' messages.json | while read -r msg; do
  msg_id=$(echo "$msg" | jq -r '.id')
  domain=$(echo "$msg" | jq -r '.from[0].email // "unknown"' | cut -d@ -f2)
  month=$(date -r "$(echo "$msg" | jq -r '.date')" +%Y-%m)
  echo "Route message $msg_id -> /Invoices/$domain/$month/"
done

How do you run the pipeline on a schedule?

You run the pipeline unattended by scoping the search to a recent window and triggering it from cron. A search filter of --after set to yesterday means each run only processes the last day's mail, so a daily 6 a.m. job never re-uploads the same attachment. Dropbox's mode: add with autorename is the safety net if an overlap ever does occur.

For headless environments — a CI runner or a server with no browser — authenticate the CLI with an API key using nylas auth config --api-key instead of the OAuth browser flow. Store the Dropbox token and the Nylas API key as separate environment secrets and rotate them independently, since they grant access to different systems. A full daily run over 50 messages finishes in seconds.

# crontab -e — file yesterday's attachments every morning at 06:00
0 6 * * * cd /opt/email-dropbox && \
  nylas email search "*" --has-attachment --after "$(date -d yesterday +%F)" \
  --json --limit 100 > messages.json && ./sync-to-dropbox.sh

Next steps