Source: https://cli.nylas.com/guides/download-attachments-powershell

# Download Email Attachments in PowerShell

Safely download, filter, scan, and process email attachments from PowerShell using Nylas CLI. Filter by file type, enforce size limits, scan with Windows Defender, batch download from multiple messages, and auto-save to SharePoint or OneDrive. Works across all major email providers.

Written by [Hazik](https://cli.nylas.com/authors/hazik) VP of Product

Updated June 13, 2026

> **TL;DR:** List emails with [`nylas email search "*" --has-attachment --json`](https://cli.nylas.com/docs/commands/email-list), read message details to get attachment IDs, download with `nylas email attachments download <att-id> <msg-id>`. Filter by extension with `Where-Object`, scan with Windows Defender, and save to SharePoint with PnP.PowerShell.

## Attachment download methods compared

PowerShell supports three methods for downloading email attachments: Exchange Web Services (EWS), Microsoft Graph API, and Nylas CLI. EWS reaches end-of-life in October 2026, Graph requires Azure AD app registration with Mail.Read scope, and the CLI authenticates with a single command. Each method differs in provider support, auth complexity, and maximum file size handling.

| Method | Auth complexity | Providers | Max file size |
| --- | --- | --- | --- |
| EWS (deprecated Oct 2026) | NTLM or OAuth + certificate | Exchange only | 10 MB default, 150 MB max |
| Microsoft Graph SDK | Azure AD app + Mail.Read scope | Microsoft 365 only | 150 MB (stream for >3 MB) |
| Nylas CLI | `nylas init` | Gmail, Outlook, Exchange, Yahoo, iCloud, IMAP | Provider limit (25-150 MB) |

With Microsoft Graph, downloading attachments over 3 MB requires the [large attachment streaming endpoint](https://learn.microsoft.com/en-us/graph/api/attachment-get) — a separate API call with content range headers. The CLI handles this automatically.

## Filter attachments by file type

Filtering email attachments by file type in PowerShell prevents downloading unwanted or dangerous files. The Nylas CLI returns attachment metadata including filename, size, and content type in JSON format. PowerShell's `Where-Object` cmdlet filters on the filename extension before any download begins, so blocked file types never touch the disk.

The script below reads a message with `nylas email read`, pipes the attachments array through `Where-Object`, and downloads only matching files. According to CISA's advisory on file-based threats, 6 of the top 10 malware file types are Windows-executable extensions (.exe,.bat,.cmd,.scr,.vbs,.js). The blocked-extensions list covers all 6.

```powershell
# Download only PDFs from a specific message
$msg = nylas email read <message-id> --json | ConvertFrom-Json

$msg.attachments |
    Where-Object { $_.filename -like "*.pdf" } |
    ForEach-Object {
        Write-Host "Downloading PDF: $($_.filename) ($([math]::Round($_.size/1024))KB)"
        nylas email attachments download $_.id $msg.id
    }

# Download images only
$msg.attachments |
    Where-Object { $_.filename -match "\.(jpg|jpeg|png|gif|webp)$" } |
    ForEach-Object { nylas email attachments download $_.id $msg.id }

# Download spreadsheets and documents
$msg.attachments |
    Where-Object { $_.filename -match "\.(xlsx|xls|csv|docx|doc)$" } |
    ForEach-Object { nylas email attachments download $_.id $msg.id }

# Block dangerous file types
$blocked = @('.exe', '.bat', '.cmd', '.ps1', '.vbs', '.js', '.msi', '.scr')
$msg.attachments |
    Where-Object {
        $ext = [IO.Path]::GetExtension($_.filename).ToLower()
        $ext -notin $blocked
    } |
    ForEach-Object { nylas email attachments download $_.id $msg.id }
```

## Enforce size limits

Enforcing attachment size limits in PowerShell prevents large files from consuming disk space or slowing down processing pipelines. Gmail caps attachments at 25 MB per message, while Outlook allows up to 150 MB. The Nylas CLI exposes the `size` field in bytes on each attachment object, so PowerShell can compare against a configurable threshold before downloading.

The following script accepts a `$MaxSizeMB` parameter (defaulting to 10 MB), lists recent messages with attachments, and skips any file exceeding the limit. Skipped files print a yellow warning with the actual size and the configured maximum.

```powershell
# Download attachments under a size limit
param([int]$MaxSizeMB = 10)

$maxBytes = $MaxSizeMB * 1024 * 1024
$emails = nylas email search "*" --has-attachment --json --limit 20 | ConvertFrom-Json

foreach ($email in $emails) {
    $detail = nylas email read $email.id --json | ConvertFrom-Json
    foreach ($att in $detail.attachments) {
        if ($att.size -gt $maxBytes) {
            Write-Host "SKIP: $($att.filename) ($([math]::Round($att.size/1MB, 1))MB > $($MaxSizeMB)MB)" `
                -ForegroundColor Yellow
            continue
        }
        Write-Host "Downloading: $($att.filename) ($([math]::Round($att.size/1KB))KB)"
        nylas email attachments download $att.id $detail.id
    }
}
```

## Scan downloads with Windows Defender

Scanning email attachments with Windows Defender before processing catches malware at the point of entry. According to Verizon's 2024 Data Breach Investigations Report, 43% of malware is delivered via email attachments. PowerShell's `Start-MpScan` cmdlet triggers a custom scan on a specific file path, and `Get-MpThreatDetection` returns any threats detected during that scan.

The script below downloads each attachment to a staging directory, scans it with Windows Defender, and moves any flagged file to a quarantine folder. It also blocks 6 high-risk extensions (.exe,.bat,.cmd,.scr,.vbs,.js) before the download starts. This two-layer approach -- extension blocking plus antivirus scanning -- stops both known and zero-day threats.

```powershell
# safe-download.ps1 -- Download and scan attachments
param(
    [string]$OutputDir = "C:\Downloads\email-attachments",
    [string]$QuarantineDir = "C:\Downloads\quarantine"
)

foreach ($dir in @($OutputDir, $QuarantineDir)) {
    if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null }
}

$emails = nylas email search "*" --has-attachment --json --limit 10 | ConvertFrom-Json

foreach ($email in $emails) {
    $detail = nylas email read $email.id --json | ConvertFrom-Json

    foreach ($att in $detail.attachments) {
        # Skip blocked extensions
        $ext = [IO.Path]::GetExtension($att.filename).ToLower()
        if ($ext -in @('.exe', '.bat', '.cmd', '.scr', '.vbs', '.js')) {
            Write-Host "BLOCKED: $($att.filename) (dangerous extension)" -ForegroundColor Red
            continue
        }

        # Download to output directory
        Push-Location $OutputDir
        nylas email attachments download $att.id $detail.id
        Pop-Location

        $filePath = Join-Path $OutputDir $att.filename
        if (-not (Test-Path $filePath)) {
            Write-Host "Download failed: $($att.filename)" -ForegroundColor Red
            continue
        }

        # Scan with Windows Defender
        Write-Host "Scanning: $($att.filename)..." -NoNewline
        Start-MpScan -ScanPath $filePath -ScanType CustomScan 2>&1 | Out-Null

        $threats = Get-MpThreatDetection -ErrorAction SilentlyContinue |
            Where-Object { $_.Resources -contains $filePath }

        if ($threats) {
            Write-Host " THREAT DETECTED" -ForegroundColor Red
            Move-Item $filePath $QuarantineDir -Force
        } else {
            Write-Host " CLEAN" -ForegroundColor Green
        }
    }
}
```

## Batch download into date-based folders

Batch downloading email attachments into date-based folders keeps large collections organized and searchable. The Nylas CLI's `nylas email search` command accepts a query string and returns matching messages, which PowerShell can iterate to download only attachments matching a file pattern. A typical accounts-payable inbox receives 200-500 invoice PDFs per month, so date-based subfolders and duplicate filename handling prevent collisions.

The script below searches for emails matching a keyword (defaulting to "invoice"), filters attachments by a glob pattern (defaulting to `*.pdf`), and saves each file into a `YYYY-MM-DD` subfolder. When a filename already exists, it appends a counter -- `invoice (1).pdf`, `invoice (2).pdf` -- to avoid overwrites.

```powershell
# batch-download.ps1 -- Batch download with organization
param(
    [string]$SearchQuery = "invoice",
    [string]$FilePattern = "*.pdf",
    [int]$Limit = 50,
    [string]$BaseDir = "C:\Downloads\invoices"
)

$emails = nylas email search $SearchQuery --json --limit $Limit | ConvertFrom-Json
$totalFiles = 0

foreach ($email in $emails) {
    $detail = nylas email read $email.id --json | ConvertFrom-Json
    $matching = $detail.attachments | Where-Object { $_.filename -like $FilePattern }
    if ($matching.Count -eq 0) { continue }

    # Create date-based subfolder
    $dateStr = ($email.date -split 'T')[0]
    $dateDir = Join-Path $BaseDir $dateStr
    if (-not (Test-Path $dateDir)) { New-Item -ItemType Directory -Path $dateDir | Out-Null }

    Push-Location $dateDir
    foreach ($att in $matching) {
        # Handle duplicate filenames
        $targetName = $att.filename
        $counter = 1
        while (Test-Path (Join-Path $dateDir $targetName)) {
            $base = [IO.Path]::GetFileNameWithoutExtension($att.filename)
            $ext = [IO.Path]::GetExtension($att.filename)
            $targetName = "$base ($counter)$ext"
            $counter++
        }
        nylas email attachments download $att.id $detail.id
        $totalFiles++
    }
    Pop-Location
}

Write-Host "Downloaded $totalFiles files to $BaseDir"
```

## Auto-save to SharePoint

Auto-saving email attachments to SharePoint eliminates the manual download-then-upload cycle that costs finance teams 5-10 minutes per invoice. The [PnP.PowerShell](https://pnp.github.io/powershell/) module provides the `Add-PnPFile` cmdlet to upload files directly to SharePoint document libraries. Combining PnP.PowerShell with Nylas CLI creates a pipeline: search for invoices, download PDFs to a temp directory, upload to SharePoint, then clean up.

The script below connects to a SharePoint site with interactive auth, searches for emails containing "invoice", downloads PDF attachments to a staging folder, and uploads each file into a month-based subfolder (e.g., `Invoices/2026-05`). PnP.PowerShell requires one-time install via `Install-Module` and Azure AD admin consent for the PnP Management Shell app.

```powershell
# Install PnP.PowerShell (one time)
Install-Module PnP.PowerShell -Scope CurrentUser -Force

# sharepoint-save.ps1 -- Download invoices, upload to SharePoint
param(
    [string]$SiteUrl = "https://company.sharepoint.com/sites/Finance",
    [string]$LibraryName = "Invoices"
)

Connect-PnPOnline -Url $SiteUrl -Interactive

$emails = nylas email search "invoice" --has-attachment --json --limit 20 |
    ConvertFrom-Json

$tempDir = "$env:TEMP\invoice-staging"
if (-not (Test-Path $tempDir)) { New-Item -ItemType Directory -Path $tempDir | Out-Null }

foreach ($email in $emails) {
    $detail = nylas email read $email.id --json | ConvertFrom-Json
    $pdfs = $detail.attachments | Where-Object { $_.filename -like "*.pdf" }

    foreach ($pdf in $pdfs) {
        Push-Location $tempDir
        nylas email attachments download $pdf.id $detail.id
        Pop-Location

        $localPath = Join-Path $tempDir $pdf.filename
        if (Test-Path $localPath) {
            $dateFolder = (Get-Date $email.date).ToString('yyyy-MM')
            Add-PnPFile -Path $localPath -Folder "$LibraryName/$dateFolder"
            Write-Host "Uploaded: $($pdf.filename) -> $LibraryName/$dateFolder" -ForegroundColor Green
            Remove-Item $localPath
        }
    }
}

Remove-Item $tempDir -Recurse -Force
```

## Auto-save to OneDrive sync folder

Saving email attachments to a OneDrive sync folder is the simplest cloud-upload approach -- no API calls, no authentication beyond the existing OneDrive desktop client. OneDrive for Business syncs files within 2 minutes of write according to Microsoft's sync documentation, so attachments appear in the cloud almost immediately. The sync folder path follows the pattern `$env:USERPROFILE\OneDrive - Company\` on Windows machines joined to an organization.

The script below lists the 10 most recent emails with attachments, downloads each attachment directly into a OneDrive subfolder, and lets the desktop client handle the upload. No PnP.PowerShell module or SharePoint connection is required.

```powershell
# onedrive-save.ps1 -- Save attachments to OneDrive sync folder
$onedrivePath = "$env:USERPROFILE\OneDrive - Company\Email Attachments"
if (-not (Test-Path $onedrivePath)) {
    New-Item -ItemType Directory -Path $onedrivePath | Out-Null
}

$emails = nylas email search "*" --has-attachment --json --limit 10 | ConvertFrom-Json

foreach ($email in $emails) {
    $detail = nylas email read $email.id --json | ConvertFrom-Json
    Push-Location $onedrivePath
    foreach ($att in $detail.attachments) {
        nylas email attachments download $att.id $detail.id
        Write-Host "Saved to OneDrive: $($att.filename)"
    }
    Pop-Location
}

Write-Host "Files will sync to OneDrive automatically"
```

## Audit attachment types and sizes

Auditing attachment types and sizes before downloading gives a clear picture of what's in your inbox and how much disk space the download will consume. A typical business mailbox accumulates 50-100 attachments per week across PDFs, spreadsheets, images, and documents. Running an audit first helps identify unexpected file types (potentially malicious.exe or.js files) and estimate total download size.

The script below reads the last 100 emails with attachments, collects filename extension and size for each file, and prints a grouped summary showing count and total megabytes per file type. The `Group-Object` cmdlet handles the aggregation, and `Measure-Object` sums the sizes within each group.

```powershell
# attachment-audit.ps1 -- Summarize attachments by type and size
$emails = nylas email search "*" --has-attachment --json --limit 100 | ConvertFrom-Json

$all = foreach ($email in $emails) {
    $detail = nylas email read $email.id --json | ConvertFrom-Json
    foreach ($att in $detail.attachments) {
        [PSCustomObject]@{
            Extension = [IO.Path]::GetExtension($att.filename).ToLower()
            SizeKB = [math]::Round($att.size / 1024, 1)
            Filename = $att.filename
            From = $email.from
        }
    }
}

Write-Host "File Type Summary:" -ForegroundColor Cyan
$all | Group-Object Extension | Sort-Object Count -Descending |
    ForEach-Object {
        $mb = [math]::Round(($_.Group | Measure-Object -Property SizeKB -Sum).Sum / 1024, 1)
        Write-Host "  $($_.Name.PadRight(8)) $($_.Count) files ($mb MB)"
    }

$totalMB = [math]::Round(($all | Measure-Object -Property SizeKB -Sum).Sum / 1024, 1)
Write-Host "Total: $($all.Count) attachments, $totalMB MB"
```

## Troubleshooting

The three most common issues when downloading email attachments in PowerShell are stale attachment IDs producing 0-byte files, Windows Defender access-denied errors when scanning without elevation, and Azure AD consent failures with PnP.PowerShell. Each has a specific fix that resolves the problem in under 2 minutes.

### Download produces a 0-byte file

The attachment ID or message ID is stale. Re-fetch the message with `nylas email read <id> --json` to get current attachment IDs. Attachment IDs change if the message is moved between folders.

### `Start-MpScan: Access denied`

Windows Defender scanning requires administrator privileges. Run PowerShell as Administrator, or use `& "C:\Program Files\Windows Defender\MpCmdRun.exe" -Scan -ScanType 3 -File $filePath` which works without elevation.

### PnP.PowerShell fails with `Connect-PnPOnline: AADSTS50001`

The PnP Management Shell app needs admin consent in your tenant. Ask your Azure AD admin to grant consent, or register a custom app with Sites.ReadWrite.All and use `Connect-PnPOnline -ClientId <id> -Interactive`.

## Frequently asked questions

These 4 questions cover the most common scenarios for downloading email attachments in PowerShell: filtering by file type, scanning for viruses, auto-saving to SharePoint, and understanding provider-specific size limits. Each answer includes the exact PowerShell command or cmdlet needed.

### How do I filter email attachments by file type in PowerShell?

Read the message with `nylas email read <id> --json | ConvertFrom-Json`, then filter: `$msg.attachments | Where-Object {$_.filename -like "*.pdf"}`. Use `-match` with regex for multiple types: `$_.filename -match "\.(pdf|docx|xlsx)$"`.

### Can I scan email attachments for viruses before saving?

Yes. After downloading, scan with Windows Defender: `Start-MpScan -ScanPath $filePath -ScanType CustomScan`. Check for detections with `Get-MpThreatDetection` and quarantine if needed. On Linux, use ClamAV.

### How do I auto-save attachments to SharePoint?

Download with Nylas CLI, then upload with PnP.PowerShell: `Add-PnPFile -Path $localPath -Folder "Library/Folder"`. For OneDrive, save to the local sync folder and it uploads automatically.

### Is there a size limit for downloading attachments?

Nylas CLI downloads any attachment the provider accepts. Gmail allows 25 MB per message, Outlook 150 MB. Check `$att.size` before downloading to skip files above your threshold.

---

## Next steps

- [Parse and extract data from emails](https://cli.nylas.com/guides/read-email-powershell) -- extract orders, tickets, structured data
- [Email and calendar automation](https://cli.nylas.com/guides/email-calendar-powershell) -- combine email and calendar workflows
- [Send email from PowerShell](https://cli.nylas.com/guides/send-email-powershell) -- compose and send HTML bodies
- [Getting started](https://cli.nylas.com/guides/getting-started) -- install the CLI and connect your first account
- [Full command reference](https://cli.nylas.com/docs/commands) -- every flag and subcommand
- [RFC 2183 -- Content-Disposition Header](https://datatracker.ietf.org/doc/html/rfc2183) — the canonical “attachment” vs “inline” semantics and filename rules
- [RFC 2046 -- MIME Media Types](https://datatracker.ietf.org/doc/html/rfc2046) — type / subtype registry parsers must respect when filtering by attachment kind

## Related hubs

- [Email agents](https://cli.nylas.com/ai-answers/email-agents.md)
- [Calendar agents](https://cli.nylas.com/ai-answers/calendar-agents.md)
- [Scheduling and availability agents](https://cli.nylas.com/ai-answers/scheduling-agents.md)
- [Contacts agents](https://cli.nylas.com/ai-answers/contacts-agents.md)
- [Notetaker and meeting agents](https://cli.nylas.com/ai-answers/notetaker-agents.md)
- [MCP agents](https://cli.nylas.com/ai-answers/mcp-agents.md)
- [Agent accounts](https://cli.nylas.com/ai-answers/agent-accounts.md)
- [Framework and language email agents](https://cli.nylas.com/ai-answers/framework-email-agents.md)
- [Email and calendar API comparisons](https://cli.nylas.com/ai-answers/ai-agent-email-api-comparisons.md)
- [Email integration and automation recipes](https://cli.nylas.com/ai-answers/email-integration-recipes.md)
- [Agent email workflows](https://cli.nylas.com/ai-answers/agent-email-workflows.md)
- [Security for email and calendar agents](https://cli.nylas.com/ai-answers/security-for-email-agents.md)
- [Operations runbooks for agents](https://cli.nylas.com/ai-answers/operations-for-email-calendar-agents.md)

## Try Nylas CLI

Install the CLI with `curl -fsSL https://cli.nylas.com/install.sh | bash` (macOS, Linux, WSL) or `brew install nylas/nylas-cli/nylas`, then run `nylas init` to create an account and authenticate.

**Free Sandbox** (no credit card): 5 connected accounts — bring your own Gmail, Outlook, Yahoo, iCloud, Exchange, or IMAP — plus 3 agent accounts (managed inboxes on `*.nylas.email`). Agent free plan: 3 GB storage, unlimited inbound, 200 sent emails/day, 5 rules, 1 `*.nylas.email` subdomain, and unlimited custom domains. Production is uncapped and requires a credit card: https://www.nylas.com/pricing/
