Guide

Parse and Extract Data from Emails in PowerShell

According to a 2023 McKinsey report, knowledge workers spend 28% of their workday reading and responding to email. Much of that is extracting data -- order confirmations, ticket numbers, shipping updates -- and typing it into another system. This guide shows how to automate that with PowerShell and Nylas CLI. Read emails, extract structured data with regex, process orders and tickets, and build inbox monitoring scripts. Works with Gmail, Outlook, Exchange, Yahoo, iCloud, and IMAP.

By Hazik

Setup

Install Nylas CLI and authenticate. See the PowerShell email guide for full install options.

irm https://cli.nylas.com/install.ps1 | iex
nylas auth config  # paste your API key

Extract order numbers from confirmation emails

E-commerce platforms send order confirmations with order IDs, amounts, and item details. This script finds order emails and extracts the structured data.

# extract-orders.ps1 -- Parse order confirmations
$orderEmails = nylas email search "order confirmation" --json --limit 20 |
    ConvertFrom-Json

$orders = $orderEmails | ForEach-Object {
    $detail = nylas email read $_.id --json | ConvertFrom-Json
    $body = $detail.body

    # Extract order number (patterns: #12345, Order-12345, ORD-12345)
    $orderNum = if ($body -match '(?:order|#|ORD)[-\s]?(\d{4,10})') {
        $matches[1]
    } else { "unknown" }

    # Extract total amount ($123.45 or USD 123.45)
    $amount = if ($body -match '(?:total|amount|charged)[\s:]*\$?([\d,]+\.?\d{0,2})') {
        $matches[1]
    } else { "unknown" }

    # Extract tracking number if present
    $tracking = if ($body -match '(?:tracking|shipment)[\s:#]*([A-Z0-9]{10,25})') {
        $matches[1]
    } else { $null }

    [PSCustomObject]@{
        Date      = $_.date
        From      = $_.from
        OrderNum  = $orderNum
        Amount    = $amount
        Tracking  = $tracking
        Subject   = $_.subject
    }
}

$orders | Format-Table Date, OrderNum, Amount, Tracking -AutoSize
$orders | Export-Csv "orders-extracted.csv" -NoTypeInformation
Write-Host "Extracted $($orders.Count) orders"

Parse support ticket notifications

Help desks like Zendesk, Freshdesk, and Jira send structured email notifications. Extract ticket IDs, priorities, and assignees to build a local tracking dashboard.

# parse-tickets.ps1 -- Extract ticket data from help desk emails
$ticketEmails = nylas email search "from:notifications@yourcompany.zendesk.com" `
    --json --limit 50 | ConvertFrom-Json

$tickets = $ticketEmails | ForEach-Object {
    $detail = nylas email read $_.id --json | ConvertFrom-Json
    $body = $detail.body
    $subject = $_.subject

    # Zendesk format: "[Ticket #12345] Subject here"
    $ticketId = if ($subject -match '\[(?:Ticket\s)?#?(\d+)\]') {
        $matches[1]
    } else { "unknown" }

    # Extract priority
    $priority = if ($body -match '(?:priority|urgency)[\s:]*(\w+)') {
        $matches[1]
    } elseif ($subject -match '\b(urgent|high|critical)\b') {
        $matches[1]
    } else { "normal" }

    # Extract status
    $status = if ($body -match '(?:status)[\s:]*(\w+(?:\s\w+)?)') {
        $matches[1]
    } else { "open" }

    [PSCustomObject]@{
        TicketId = $ticketId
        Subject  = ($subject -replace '\[.*?\]\s*', '')
        Priority = $priority
        Status   = $status
        Date     = $_.date
    }
}

Write-Host "Ticket Summary:" -ForegroundColor Cyan
$tickets | Group-Object Priority | Sort-Object Count -Descending |
    ForEach-Object { Write-Host "  $($_.Name): $($_.Count)" }

$tickets | Format-Table TicketId, Priority, Status, Subject -AutoSize

Continuous inbox monitoring

Process new emails as they arrive by polling at regular intervals. This pattern is the foundation for email-driven automation.

# inbox-processor.ps1 -- Process new emails as they arrive
param([int]$PollIntervalSeconds = 30)

$seenIds = @{}

Write-Host "Monitoring inbox... (poll every $($PollIntervalSeconds)s)" -ForegroundColor Cyan

while ($true) {
    try {
        $emails = nylas email list --unread --json --limit 20 | ConvertFrom-Json

        foreach ($email in $emails) {
            if ($seenIds.ContainsKey($email.id)) { continue }

            $detail = nylas email read $email.id --json | ConvertFrom-Json

            # Route based on sender
            switch -Regex ($email.from) {
                'orders@'     { Write-Host "ORDER: $($email.subject)" -ForegroundColor Green }
                'alerts@'     { Write-Host "ALERT: $($email.subject)" -ForegroundColor Red }
                'zendesk.com' { Write-Host "TICKET: $($email.subject)" -ForegroundColor Yellow }
                default       { Write-Host "NEW: $($email.subject)" -ForegroundColor Gray }
            }

            $seenIds[$email.id] = $true
        }

        # Prevent unbounded memory growth
        if ($seenIds.Count -gt 1000) {
            $keep = $seenIds.Keys | Select-Object -Last 500
            $seenIds = @{}
            $keep | ForEach-Object { $seenIds[$_] = $true }
        }
    } catch {
        Write-Host "Error: $_" -ForegroundColor Red
    }

    Start-Sleep -Seconds $PollIntervalSeconds
}

Extract shipping tracking updates

Carrier emails from UPS, FedEx, and USPS contain tracking numbers and delivery estimates. Each carrier uses a different tracking number format.

# shipping-tracker.ps1 -- Extract tracking info from carrier emails
$carriers = @{
    'ups'   = @{ Pattern = '1Z[A-Z0-9]{16}'; Sender = '*@ups.com' }
    'fedex' = @{ Pattern = '\b\d{12,22}\b'; Sender = '*@fedex.com' }
    'usps'  = @{ Pattern = '\b(9[234]\d{20,22})\b'; Sender = '*@usps.gov' }
}

$emails = nylas email list --json --limit 100 | ConvertFrom-Json

$shipments = foreach ($email in $emails) {
    foreach ($carrier in $carriers.GetEnumerator()) {
        if ($email.from -like $carrier.Value.Sender) {
            $detail = nylas email read $email.id --json | ConvertFrom-Json
            if ($detail.body -match $carrier.Value.Pattern) {
                $deliveryDate = if ($detail.body -match '(?:deliver|expected).*?(\d{1,2}/\d{1,2}/\d{2,4})') {
                    $matches[1]
                } else { "TBD" }

                [PSCustomObject]@{
                    Carrier     = $carrier.Key.ToUpper()
                    Tracking    = $matches[0]
                    DeliveryEst = $deliveryDate
                    Subject     = $email.subject
                }
            }
        }
    }
}

$shipments | Format-Table Carrier, Tracking, DeliveryEst, Subject -AutoSize

Parse financial transaction emails

Bank alerts and payment processors send transaction notifications with amounts, merchant names, and account identifiers.

# financial-parser.ps1 -- Extract transaction data from bank emails
$bankEmails = nylas email search "from:alerts@yourbank.com" --json --limit 50 |
    ConvertFrom-Json

$transactions = $bankEmails | ForEach-Object {
    $detail = nylas email read $_.id --json | ConvertFrom-Json
    $body = $detail.body

    $amount = if ($body -match '\$\s?([\d,]+\.\d{2})') { $matches[1] -replace ',' }
              else { $null }

    $merchant = if ($body -match "(?:at|merchant|payee)[\s:]+([A-Za-z0-9\s&']+?)(?:\.|,|\n)") {
        $matches[1].Trim()
    } else { "Unknown" }

    $type = if ($body -match 'debit|purchase|withdrawal') { "Debit" }
            elseif ($body -match 'credit|deposit|refund') { "Credit" }
            else { "Unknown" }

    if ($amount) {
        [PSCustomObject]@{
            Date=$_.date; Type=$type; Amount=[decimal]$amount; Merchant=$merchant
        }
    }
}

$totalDebits = ($transactions | Where-Object { $_.Type -eq "Debit" } |
    Measure-Object -Property Amount -Sum).Sum

Write-Host "Transactions: $($transactions.Count), Total debits: $totalDebits"
$transactions | Sort-Object Amount -Descending | Format-Table -AutoSize

Route emails to different handlers

Many SaaS platforms send emails with consistent formats. Build a router that dispatches to different handlers based on sender or subject pattern.

# email-router.ps1 -- Route emails to processing handlers
$handlers = @{
    'alerts@pagerduty.com' = {
        param($email, $detail)
        if ($detail.body -match 'Incident #(\d+)') {
            Write-Host "PagerDuty incident #$($matches[1])" -ForegroundColor Red
        }
    }
    'noreply@github.com' = {
        param($email, $detail)
        if ($email.subject -match '\[(.+?)\] (.+?) #(\d+)') {
            Write-Host "GitHub: $($matches[1]) $($matches[2]) #$($matches[3])" -ForegroundColor Cyan
        }
    }
    'forms@company.com' = {
        param($email, $detail)
        $fields = @{}
        [regex]::Matches($detail.body, '(\w[\w\s]+?):\s*(.+?)(?:\n|$)') |
            ForEach-Object { $fields[$_.Groups[1].Value.Trim()] = $_.Groups[2].Value.Trim() }
        Write-Host "Form: $($fields.Count) fields" -ForegroundColor Yellow
        $fields.GetEnumerator() | ForEach-Object { Write-Host "  $($_.Key): $($_.Value)" }
    }
}

$emails = nylas email list --unread --json --limit 30 | ConvertFrom-Json
foreach ($email in $emails) {
    $handler = $handlers.GetEnumerator() |
        Where-Object { $email.from -like "*$($_.Key)*" } | Select-Object -First 1
    if ($handler) {
        $detail = nylas email read $email.id --json | ConvertFrom-Json
        & $handler.Value $email $detail
    }
}

Export parsed data to SQL Server

# email-to-sql.ps1 -- Parse emails and insert into database
$emails = nylas email search "order confirmation" --json --limit 50 |
    ConvertFrom-Json

foreach ($email in $emails) {
    $detail = nylas email read $email.id --json | ConvertFrom-Json
    $orderNum = if ($detail.body -match 'order\s*#?\s*(\d{4,10})') { $matches[1] } else { continue }
    $amount = if ($detail.body -match '\$([\d,]+\.\d{2})') { $matches[1] -replace ',' } else { "0" }

    $query = "IF NOT EXISTS (SELECT 1 FROM Orders WHERE OrderNumber=@OrderNum) " +
             "INSERT INTO Orders (OrderNumber,Amount,EmailDate) VALUES (@OrderNum,@Amount,@EmailDate)"

    try {
        Invoke-Sqlcmd -ServerInstance "db.company.local" -Database "OrderTracking" -Query $query -Variable "OrderNum=$orderNum","Amount=$amount","EmailDate=$($email.date)"
        Write-Host "Inserted #$orderNum" -ForegroundColor Green
    } catch {
        Write-Host "Failed #$orderNum : $_" -ForegroundColor Red
    }
}

Frequently asked questions

How do I extract data from emails in PowerShell?

Use nylas email read <id> --json | ConvertFrom-Json to get the full email body. Then apply regex with -match to extract order numbers, tracking codes, amounts, or any structured data. PowerShell's $matches automatic variable captures the matched groups.

Can I process incoming emails automatically with PowerShell?

Yes. Build a polling loop that calls nylas email list --unread --json every 30-60 seconds. Track seen message IDs in a hashtable. Process new arrivals with your extraction logic. Schedule the script with Task Scheduler for persistent monitoring.

How do I parse HTML email bodies in PowerShell?

For simple extraction, use regex with -match on the raw HTML. For complex HTML, install HtmlAgilityPack from NuGet and query DOM elements. In most cases, regex is faster since you're extracting specific values, not navigating the full document tree.


Next steps