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

# Sync Email to Amazon S3 from the Terminal

Long-term email archives belong in object storage, not a mailbox. S3 stores a message for a fraction of a cent a month and keeps it queryable with Athena later. The Nylas CLI exports each message as JSON; the AWS CLI uploads it. This guide builds an email-to-S3 sync that partitions objects by date, uploads attachments alongside the JSON, and runs incrementally so each run only ships the new mail, across six providers.

Written by [Aaron de Mello](https://cli.nylas.com/authors/aaron-de-mello) Senior Engineering Manager

Updated June 8, 2026

> **TL;DR:** Export messages with `nylas email search --json`, write one JSON object per message, and upload with `aws s3 cp` into a date-partitioned prefix like `s3://bucket/email/2026/06/08/`. Pull attachments with `nylas email attachments` and store them beside the JSON. Run it incrementally with `newer_than` so each run ships only new mail. S3 standard storage runs about $0.023 per GB-month.

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

## How do you sync email to S3?

You sync email to S3 by exporting each message as a JSON file and uploading it with the AWS CLI. The CLI returns structured messages with `nylas email search --json`, and `aws s3 cp` writes each one to a key in your bucket. Because both ends are command-line, the whole sync is a shell loop — pull, write, upload — with no SDK and no mail server.

Partition the keys by date so retrieval and lifecycle rules stay cheap. A prefix like `email/YYYY/MM/DD/<message-id>.json` lets you query a day's mail without listing the whole bucket and lets S3 lifecycle policies tier old data to cheaper storage. The AWS CLI's S3 commands are documented in the [AWS CLI S3 reference](https://docs.aws.amazon.com/cli/latest/reference/s3/).

```bash
# Export the day's messages and upload each as a date-partitioned object
day=$(date -u +%Y/%m/%d)
nylas email search "newer_than:1d" --json --limit 500 \
  | jq -c '.[]' \
  | while read -r msg; do
      id=$(echo "$msg" | jq -r '.id')
      echo "$msg" | aws s3 cp - "s3://my-archive/email/$day/$id.json"
    done
```

## How do you store attachments alongside the JSON?

Store attachments by listing them with `nylas email attachments list <msg-id> --json`, downloading each with `nylas email attachments download <att-id> <msg-id>`, and uploading the files next to the message JSON. Keeping each attachment under the same date-and-message prefix means a message and its files live together, so a later restore or audit finds everything in one place. Large attachments are exactly what you want in S3 rather than a mailbox.

Name attachment keys with the message ID and the original filename so they're traceable back to the email. For an archive that must be tamper-evident, enable S3 Object Lock or versioning on the bucket, and turn on default encryption so objects are encrypted at rest. The AWS credentials here are separate from the mailbox grant the CLI manages.

```bash
# List a message's attachments, download each, then upload beside its JSON
nylas email attachments list "$id" --json | jq -r '.[].id' \
  | while read -r att; do
      nylas email attachments download "$att" "$id" --output "./att/$id/"
    done
aws s3 cp "./att/$id/" "s3://my-archive/email/$day/$id/" --recursive
```

## How do you run it incrementally?

Run it incrementally by scoping the search to a time window — `newer_than:1d` for a daily job — so each run ships only the mail since the last one. That keeps each run small and cheap and avoids re-uploading the entire mailbox nightly. Because object keys include the message ID, an accidental re-upload simply overwrites the same key rather than creating a duplicate.

Schedule the sync with cron, a CI job, or a Kubernetes CronJob, and grant the runner an IAM role scoped to `s3:PutObject` on just the archive bucket — least privilege for the one action it needs. S3 standard storage is roughly $0.023 per GB-month, so a year of text-heavy email costs cents; attachments dominate the bill, which is another reason to tier them with a lifecycle rule.

## Next steps

- [Back up emails to JSON](https://cli.nylas.com/guides/backup-emails-to-json) — the local export this builds on
- [Load email into Postgres](https://cli.nylas.com/guides/email-to-postgres) — a queryable store instead of files
- [Parse email attachments](https://cli.nylas.com/guides/parse-email-attachments) — work with downloaded files
- [Email from a Kubernetes CronJob](https://cli.nylas.com/guides/kubernetes-cronjob-email) — schedule the sync in a cluster
- [Full command reference](https://cli.nylas.com/docs/commands) — every flag and subcommand documented
