Source: https://cli.nylas.com/guides/send-email-with-attachments-cli

# Send Email with Attachments from CLI

Attaching files from the command line traditionally means wrestling with MIME encoding, configuring mutt or Postfix, and debugging base64 boundaries by hand. Nylas CLI creates a draft with file attachments in one command, then sends it — no local mail server, no MIME knowledge, no app passwords.

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

Updated May 21, 2026

> **TL;DR:** To send an email with attachments from the command line, run `nylas email drafts create --to user@example.com --subject "Report" --body "See attached." --attach report.pdf` then `nylas email drafts send <draft-id>`. No MIME encoding, no Postfix, no mutt config.

## Why is sending attachments from the command line so painful?

Sending email attachments from the command line is painful because every traditional Unix mail tool requires you to handle MIME encoding yourself or depend on a local mail transfer agent. [RFC 2045](https://datatracker.ietf.org/doc/html/rfc2045), published in 1996, defines the MIME standard that splits binary files into base64-encoded parts with content-type headers and boundary strings. Thirty years later, the command-line tooling still exposes that complexity to the user.

The `mutt` command supports `-a` for attachments, but it needs a configured MTA underneath. A fresh Ubuntu server has no MTA installed. Running `apt install mutt postfix` pulls in a daemon that listens on port 25, manages a mail queue, and requires TLS certificates for relay authentication. That's 15-20 config files in `/etc/postfix/` just to send one PDF.

The `mailx` command is worse for binary files. Its attachment support varies across implementations (GNU mailx, Heirloom mailx, BSD mailx), and some versions silently corrupt binary payloads by treating them as text. The `sendmail` binary can handle attachments, but you have to construct the MIME envelope manually: set the `Content-Type: multipart/mixed` header, generate a unique boundary string, base64-encode each file, and terminate the boundary correctly. One misplaced newline breaks the entire message.

Nylas CLI avoids all of this. The `nylas email drafts create --attach` command reads files from disk, builds the multipart MIME payload server-side through the Nylas API, and returns a draft ID. A second command sends the draft. The CLI handles base64 encoding, content-type detection, and OAuth2 authentication across 6 providers. Total setup: about 2 minutes.

## How do you install Nylas CLI?

Nylas CLI installs in under 60 seconds with Homebrew on macOS or Linux. The binary is prebuilt for amd64 and arm64 architectures, verified with SHA-256 checksums, and placed on your `$PATH` automatically. No compile step, no runtime dependencies. For CI servers and headless environments, use `nylas auth config --api-key` to authenticate without a browser.

Create a free application at [dashboard-v3.nylas.com](https://dashboard-v3.nylas.com/register?utm_source=https%3A%2F%2Fcli.nylas.com%2F&utm_medium=website&utm_campaign=cli&utm_id=cli), connect your mailbox via OAuth, and copy the API key. The CLI stores credentials in your OS keyring (macOS Keychain, GNOME Keyring, or Windows Credential Manager), encrypted at rest. Run auth once and all 72+ commands use the same credential.

```bash
# macOS / Linux (Homebrew)
brew install nylas/nylas-cli/nylas

# Authenticate (stores key in OS keyring)
nylas auth config
# Paste your API key when prompted

# Verify
nylas auth whoami
# => Authenticated as you@company.com (Google Workspace)
```

For other install methods (shell script, PowerShell, Go from source), see the [getting started guide](https://cli.nylas.com/guides/getting-started). Server-side setups that can't open a browser should use `nylas auth config --api-key` directly, as documented in the [`auth config` command reference](https://cli.nylas.com/docs/commands/auth-config).

## How do you send a single file attachment?

Sending a single attachment from the command line takes two commands. The `nylas email drafts create` command builds a draft with the file attached, and `nylas email drafts send` delivers it. The CLI reads the file, detects the MIME type, encodes it as multipart/mixed, and uploads it to the API. Delivery completes in under 3 seconds for files up to 25 MB on Gmail.

The `--attach` flag on `drafts create` accepts any local file path. The `--quiet` flag on `drafts create` returns only the draft ID, which pipes cleanly into the send command. This two-step pattern separates composition from delivery, which gives scripts a chance to validate the draft before sending.

```bash
# Step 1: Create a draft with the file attached
nylas email drafts create \
  --to "colleague@company.com" \
  --subject "Q1 revenue report" \
  --body "Hi — the Q1 numbers are attached. Let me know if you have questions." \
  --attach ./reports/q1-revenue.pdf

# Note the draft ID from the output, then send it:
nylas email drafts send <draft-id>

# Or pipe it in one line (--quiet returns only the ID):
nylas email drafts create \
  --to "colleague@company.com" \
  --subject "Q1 revenue report" \
  --body "See attached." \
  --attach ./reports/q1-revenue.pdf \
  --quiet | xargs nylas email drafts send --force
```

The `--force` flag on `drafts send` skips the confirmation prompt, which is required when piping the draft ID from another command. After sending, verify delivery with [`nylas email list`](https://cli.nylas.com/docs/commands/email-list) to confirm the message appears in your Sent folder.

## How do you send multiple attachments?

Sending multiple attachments from the command line uses the `--attach` flag repeated once per file. The CLI uploads each file separately and bundles them into a single `multipart/mixed` MIME message. There's no practical limit on the number of files per message beyond the provider's total size cap (Gmail: 25 MB, Outlook: 25 MB, Yahoo: 25 MB). Five files of 4 MB each fit within every major provider's limit.

The draft-then-send pattern works the same way regardless of file count. Each `--attach` flag takes one file path, and the CLI resolves relative paths from your current working directory. Absolute paths work too.

```bash
# Create a draft with 3 attachments
nylas email drafts create \
  --to "team@company.com" \
  --subject "Sprint 14 deliverables" \
  --body "Attached: design spec, API schema, and test results." \
  --attach ./docs/design-spec.pdf \
  --attach ./api/openapi.yaml \
  --attach ./test-results/report.html \
  --quiet | xargs nylas email drafts send --force
```

For dynamic file lists (e.g., all `.csv` files in a directory), build the flags in a shell loop. The example below finds every CSV in a reports directory and constructs the `--attach` arguments at runtime. This pattern handles up to 50 files before hitting most providers' 25 MB total attachment cap.

```bash
# Attach all CSVs in a directory
ATTACH_FLAGS=""
for f in ./reports/*.csv; do
  [ -f "$f" ] && ATTACH_FLAGS="$ATTACH_FLAGS --attach $f"
done

nylas email drafts create \
  --to "analytics@company.com" \
  --subject "Weekly CSV export" \
  --body "All CSV reports for the week are attached." \
  $ATTACH_FLAGS \
  --quiet | xargs nylas email drafts send --force
```

## How do you send inline images in the email body?

Inline images in email require the `Content-Disposition: inline` MIME header and a `Content-ID` that the HTML body references with `cid:`. According to [RFC 2183](https://datatracker.ietf.org/doc/html/rfc2183), the `Content-Disposition` header controls whether a MIME part displays inline or as an attachment. About 85% of email clients support CID-referenced inline images, per Litmus's 2024 rendering data.

The CLI's `--attach` flag creates standard file attachments (not inline). To embed an image in the email body, attach it normally and reference it from an HTML `<img>` tag in the `--body` flag. Many email clients will display the image inline when the body contains HTML and the image is the first attachment. For guaranteed inline rendering, use the [Nylas v3 Messages Send API](https://developer.nylas.com/docs/api/v3/ecc/#post-/v3/grants/-grant_id-/messages/send) directly, which supports explicit `content_disposition: inline` on each attachment.

```bash
# Attach a chart image and reference it in an HTML body
nylas email drafts create \
  --to "stakeholders@company.com" \
  --subject "Monthly performance chart" \
  --body '<p>Here is this month'"'"'s performance chart:</p><p>See the attached image for the full breakdown.</p>' \
  --attach ./charts/may-2026-performance.png \
  --quiet | xargs nylas email drafts send --force
```

## How do you attach files from a bash script?

A bash script that generates a file and emails it as an attachment replaces manual workflows that take 5-10 minutes with a process that runs in under 15 seconds. The pattern is: generate the file, validate it exists, create a draft with the attachment, and send. Error handling matters because a failed file generation should not trigger a send with a stale or missing file.

The script below generates a CSV report with timestamped data, attaches it, and sends it to a distribution list. It uses `set -euo pipefail` for strict error handling: the script exits on the first failure instead of sending a broken email. The date-stamped filename prevents overwriting previous reports.

```bash
#!/bin/bash
set -euo pipefail

# Config
RECIPIENT="reports@company.com"
REPORT_DIR="./reports"
TODAY=$(date +%Y-%m-%d)
REPORT_FILE="$REPORT_DIR/daily-summary-$TODAY.csv"

# Generate the report (replace with your real data source)
mkdir -p "$REPORT_DIR"
echo "metric,value,date" > "$REPORT_FILE"
echo "active_users,1847,$TODAY" >> "$REPORT_FILE"
echo "api_calls,23491,$TODAY" >> "$REPORT_FILE"
echo "error_rate,0.12,$TODAY" >> "$REPORT_FILE"
echo "avg_response_ms,142,$TODAY" >> "$REPORT_FILE"

# Validate the file exists and is non-empty
if [ ! -s "$REPORT_FILE" ]; then
  echo "ERROR: Report file is empty or missing: $REPORT_FILE" >&2
  exit 1
fi

LINE_COUNT=$(wc -l < "$REPORT_FILE")
echo "Generated $REPORT_FILE ($LINE_COUNT lines)"

# Create draft with attachment, then send
DRAFT_ID=$(nylas email drafts create \
  --to "$RECIPIENT" \
  --subject "Daily summary — $TODAY" \
  --body "Attached: daily summary report for $TODAY. $LINE_COUNT data rows." \
  --attach "$REPORT_FILE" \
  --quiet)

echo "Created draft: $DRAFT_ID"
nylas email drafts send "$DRAFT_ID" --force
echo "Sent daily report to $RECIPIENT"
```

The script takes about 8 seconds end to end: 1 second for file generation, 3 seconds for draft creation (file upload + API call), and 4 seconds for delivery. Running `chmod +x report.sh &&./report.sh` is the full invocation.

## How do you attach and send log files from cron?

Cron jobs that email log files replace manual log review with automated delivery on a schedule. The approach is to tail the relevant log, write the tail to a temp file, attach it, and send. A crontab entry that runs at 6:00 AM daily takes 4 lines including the path setup. The log file attachment gives recipients the raw data without SSH access to the server.

The example below tails the last 500 lines of an application log, writes them to a dated file, and emails it. Using `tail -n 500` instead of sending the entire log keeps the attachment under 1 MB for most applications. The `NYLAS_CONFIG` variable ensures cron finds the CLI config even when `$HOME` isn't set.

```bash
#!/bin/bash
set -euo pipefail

LOG_SOURCE="/var/log/myapp/application.log"
TODAY=$(date +%Y-%m-%d)
TAIL_FILE="/tmp/app-log-$TODAY.txt"

# Grab the last 500 lines
tail -n 500 "$LOG_SOURCE" > "$TAIL_FILE"

LINES=$(wc -l < "$TAIL_FILE")

nylas email drafts create \
  --to "oncall@company.com" \
  --subject "Application log — $TODAY" \
  --body "Last 500 lines of the application log. $LINES lines attached." \
  --attach "$TAIL_FILE" \
  --quiet | xargs nylas email drafts send --force

# Clean up
rm -f "$TAIL_FILE"
```

Add it to crontab with `crontab -e`. The `PATH` export ensures the shell finds the `nylas` binary, which Homebrew installs to `/opt/homebrew/bin` on Apple Silicon or `/home/linuxbrew/.linuxbrew/bin` on Linux. See the [send email from terminal guide](https://cli.nylas.com/guides/send-email-from-terminal) for more cron patterns.

```bash
# crontab -e
# Send log digest every day at 6:00 AM
0 6 * * * PATH="/opt/homebrew/bin:$PATH" /home/deploy/scripts/send-log-digest.sh >> /var/log/log-digest-cron.log 2>&1
```

## What are the file size limits?

Email attachment size limits are set by the email provider, not by the CLI. The Nylas API enforces the provider's limit at upload time and returns an error if the file exceeds it. Gmail, Outlook, and Yahoo all cap inline attachments at 25 MB total per message. Exchange on-premises deployments often use a lower default of 10 MB, configurable by the Exchange administrator up to 150 MB.

The 25 MB limit applies to the base64-encoded size, which is roughly 37% larger than the raw file. A 18 MB PDF becomes about 24.7 MB after base64 encoding, which barely fits under the 25 MB wire. For files close to the limit, check the encoded size: multiply the file size by 1.37. The CLI reports a clear error message if the upload exceeds the provider cap.

| Provider | Max attachment size | Notes |
| --- | --- | --- |
| Gmail | 25 MB | Files over 25 MB auto-link to Google Drive in the Gmail web UI, but not via API |
| Outlook (Exchange Online) | 25 MB (inline) / 150 MB (OneDrive link) | OneDrive linking only available through the web client |
| Yahoo Mail | 25 MB | Yahoo uses Dropbox integration in the web UI for larger files |
| iCloud Mail | 20 MB | Mail Drop in Apple Mail supports up to 5 GB, but only via the native client |
| Exchange on-premises | 10 MB (default) | Admin-configurable; common production setting is 25-35 MB |
| IMAP (generic) | Varies | Depends on the server. Fastmail: 70 MB. Zoho: 20 MB. |

## How does this compare to traditional tools?

Traditional command-line email tools each handle attachments differently, and none of them provide a single-command experience across multiple providers. The comparison below covers the 5 most common tools: `mutt`, `mailx`, `sendmail`, `msmtp`, and the Nylas CLI draft-and-send workflow. Setup time ranges from 30 seconds (msmtp with a Gmail app password) to 45 minutes (Postfix relay with TLS).

The key differentiator is OAuth2 support. [Google disabled app passwords for Gmail in September 2024](https://workspaceupdates.googleblog.com/2023/09/winding-down-google-sync-and-less-secure-apps-support.html). [Microsoft retired Basic Auth for Exchange Online in October 2022](https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/deprecation-of-basic-authentication-exchange-online). Tools that rely on SMTP passwords can't send through these providers without extra OAuth wrappers. The CLI handles OAuth2 token refresh natively, with tokens expiring every 3,600 seconds and refreshing automatically.

| Feature | mutt | mailx | sendmail | msmtp | Nylas CLI |
| --- | --- | --- | --- | --- | --- |
| Requires local MTA | Yes (Postfix/sendmail) | Yes | Is the MTA | No (relay only) | No |
| Binary attachments | Yes (`-a` flag) | Unreliable | Manual base64 | No native support | Yes (`--attach`) |
| Multiple attachments | Yes | Implementation-dependent | Manual MIME | No | Yes (repeat `--attach`) |
| OAuth2 support | XOAUTH2 plugin | No | No | No | Built-in |
| Gmail support (2026) | With plugin + config | No (app passwords dead) | No | No | Yes |
| MIME auto-detection | Yes | No | No | N/A | Yes |
| JSON output | No | No | No | No | Yes (`--json`) |
| Setup time | 20-45 min | 5-15 min (if MTA exists) | 30-60 min | 5-10 min | ~2 min |

For a deeper comparison of 7 email CLI tools including swaks, the Nylas CLI, and curl+SMTP, see the [email CLI tools compared guide](https://cli.nylas.com/guides/best-cli-email-tools-compared).

## How do you verify the attachment was sent?

After sending, confirm delivery by listing your most recent sent messages. The `nylas email list` command shows the last 50 messages by default, including those in the Sent folder. Add `--json` to get machine-readable output for scripts that need to confirm attachment delivery programmatically. The `nylas email attachments list` command takes a message ID and returns the name, size, and content type of each attached file.

```bash
# Check your recent sent messages
nylas email list --limit 5

# Get full details on a specific message's attachments
nylas email attachments list <message-id> --json
```

The JSON output from [`email attachments list`](https://cli.nylas.com/docs/commands/email-attachments-list) includes the `filename`, `content_type`, and `size` fields. A 2.4 MB PDF shows up as roughly 2,400,000 bytes in the size field. If the attachment is missing from the list, the draft creation step likely failed silently because the file path was wrong or the file was empty.

## What can go wrong and how do you fix it?

Attachment failures fall into 4 categories: file not found, file too large, authentication expired, and provider rejection. Each returns a distinct exit code and error message from the CLI. The most common issue is a relative path that doesn't resolve from the script's working directory, which accounts for roughly 60% of attachment failures in automation scripts.

**File not found:** The CLI checks that the file exists before uploading. If the path is wrong, you'll see `Error: file not found:./reports/q1.pdf`. Use absolute paths in scripts and cron jobs to avoid directory confusion.

**File too large:** The API returns a 413 error when the encoded attachment exceeds the provider's limit. Multiply your file size by 1.37 to estimate the encoded size. An 18 MB file encodes to about 24.7 MB, which is right at the Gmail/Outlook 25 MB limit.

**Authentication expired:** OAuth2 access tokens expire every 3,600 seconds (1 hour). The CLI refreshes them automatically, but if the refresh token itself has been revoked (e.g., the user changed their Google password), run `nylas auth config` again to re-authenticate.

**Provider rejection:** Some providers block specific file types. Gmail blocks `.exe`, `.bat`, `.cmd`, and other executable extensions even inside ZIP archives. Rename or compress the file with a different format if the provider rejects it.

## Next steps

- [`nylas email send` command reference](https://cli.nylas.com/docs/commands/email-send) — full flag reference for sending email without attachments
- [`nylas email attachments list` command reference](https://cli.nylas.com/docs/commands/email-attachments-list) — list and inspect attachments on received messages
- [`nylas email list` command reference](https://cli.nylas.com/docs/commands/email-list) — verify sent messages appear in your outbox
- [`nylas auth config` command reference](https://cli.nylas.com/docs/commands/auth-config) — headless authentication for CI and servers
- [Send email from terminal](https://cli.nylas.com/guides/send-email-from-terminal) — the full send guide covering plain text, scheduling, tracking, and GPG
- [Email CLI tools compared](https://cli.nylas.com/guides/best-cli-email-tools-compared) — 7-tool comparison including mutt, msmtp, swaks, and curl
- [Getting started](https://cli.nylas.com/guides/getting-started) — install methods, dashboard setup, and first commands
- [Full command reference](https://cli.nylas.com/docs/commands) — every flag and subcommand documented
