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

# 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](https://cli.nylas.com/authors/qasim-muhammad) Staff SRE

Updated June 9, 2026

> **TL;DR:** Find messages that carry files with `nylas email search "*" --has-attachment`, list each message's parts with `nylas email attachments list`, download them with `nylas email attachments download`, then upload each file to the Dropbox `/2/files/upload` endpoint with a bearer token. The CLI handles the mailbox; Dropbox handles the storage. There's one gotcha on the upload header that quietly truncates large files — it's covered below.

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 [`nylas email attachments`](https://cli.nylas.com/docs/commands/email-attachments-list).

## 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](https://www.dropbox.com/developers/documentation/http/documentation).

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.

```bash
# 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.

```bash
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](https://www.dropbox.com/developers/reference/data-ingress-guide).

```bash
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.

```bash
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.

```bash
# 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

- [Parse email attachments](https://cli.nylas.com/guides/parse-email-attachments) — filter parts by type before uploading
- [Save attachments to Google Drive](https://cli.nylas.com/guides/email-to-google-drive) — the same flow into Drive
- [Sync email to S3](https://cli.nylas.com/guides/sync-email-to-s3) — archive attachments to object storage
- [Email to Snowflake](https://cli.nylas.com/guides/email-to-snowflake) — load message metadata into a warehouse
- [Getting started](https://cli.nylas.com/guides/getting-started) — install with `brew install nylas/nylas-cli/nylas` and authenticate
- [Full command reference](https://cli.nylas.com/docs/commands) — every flag and subcommand documented
- Dropbox [HTTP API reference](https://www.dropbox.com/developers/documentation/http/documentation), the [OAuth guide](https://developers.dropbox.com/oauth-guide), and the [MIME RFC 2045](https://datatracker.ietf.org/doc/html/rfc2045) for attachment encoding
