Guide
Send Email Notifications from CI/CD Pipelines
According to the 2024 State of DevOps Report, teams that get fast feedback on build failures deploy 208x more frequently. This guide shows how to send build notifications, deployment reports, and test failure alerts from GitHub Actions, Azure DevOps, and Jenkins pipelines using PowerShell and Nylas CLI. Works with Gmail, Outlook, Exchange, Yahoo, iCloud, and IMAP -- no SMTP relay needed.
By Pouya Sanooei
Why not SMTP from CI/CD?
Most CI/CD email notification setups rely on SMTP relays or provider plugins. Both have problems:
- SMTP relays require firewall rules -- your runner needs outbound access to port 587 or 465, which many enterprise networks block
- Gmail and Outlook block basic auth -- Google disabled "less secure app access" in September 2024; Microsoft is deprecating basic auth for SMTP
- Credentials in CI secrets -- SMTP passwords rot, and rotating them means updating every pipeline
- Plugin lock-in -- GitHub's email action, Azure DevOps' built-in notifications, and Jenkins' SMTP plugin each have different configs
Nylas CLI sends email through OAuth2 via any provider. One binary, one API key, every CI platform.
Pipeline setup (all platforms)
Install Nylas CLI and authenticate using an API key. See the PowerShell email guide for full install details.
# Linux/macOS runners
curl -fsSL https://cli.nylas.com/install.sh | bash
# Windows runners
irm https://cli.nylas.com/install.ps1 | iex
# Authenticate with API key (headless, no browser needed)
nylas auth config --api-key $env:NYLAS_API_KEYGitHub Actions: build failure notification
This workflow sends an email when a build fails on main. The email includes the commit SHA, branch, actor, and a direct link to the failed run.
# .github/workflows/build-notify.yml
name: Build with Email Notification
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: npm ci && npm run build
- name: Install Nylas CLI
if: failure()
run: |
curl -fsSL https://cli.nylas.com/install.sh | bash
echo "$HOME/.nylas/bin" >> $GITHUB_PATH
- name: Send failure notification
if: failure()
shell: pwsh
env:
NYLAS_API_KEY: ${{ secrets.NYLAS_API_KEY }}
run: |
nylas auth config --api-key $env:NYLAS_API_KEY
$subject = "Build FAILED: ${{ github.repository }} @ ${{ github.sha }}"
$body = @"
Build failed on ${{ github.ref_name }}
Repository: ${{ github.repository }}
Commit: ${{ github.sha }}
Author: ${{ github.actor }}
Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
Triggered by push from ${{ github.actor }} at $(Get-Date -Format 'yyyy-MM-dd HH:mm UTC').
"@
nylas email send `
--to "team@company.com" `
--subject $subject `
--body $body `
--yesGitHub Actions: deployment report
After a successful deployment, send an HTML report with environment details, duration, and a git changelog.
# .github/workflows/deploy-report.yml
name: Deploy and Report
on:
workflow_dispatch:
inputs:
environment:
description: "Target environment"
required: true
type: choice
options: [staging, production]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for changelog
- name: Record start time
id: timer
run: echo "start=$(date +%s)" >> $GITHUB_OUTPUT
- name: Deploy to ${{ inputs.environment }}
run: ./scripts/deploy.sh ${{ inputs.environment }}
- name: Install Nylas CLI
run: |
curl -fsSL https://cli.nylas.com/install.sh | bash
echo "$HOME/.nylas/bin" >> $GITHUB_PATH
- name: Send deployment report
shell: pwsh
env:
NYLAS_API_KEY: ${{ secrets.NYLAS_API_KEY }}
run: |
nylas auth config --api-key $env:NYLAS_API_KEY
# Calculate deployment duration
$startTime = ${{ steps.timer.outputs.start }}
$endTime = [int](Get-Date -UFormat %s)
$duration = $endTime - $startTime
# Get changelog (commits since last tag)
$changelog = git log --oneline $(git describe --tags --abbrev=0)..HEAD |
ForEach-Object { "<li>$_</li>" }
$html = @"
<html><body style="font-family:system-ui;background:#0f172a;color:#e2e8f0;padding:24px">
<h1 style="color:#4ade80">Deployed to ${{ inputs.environment }}</h1>
<table style="border-collapse:collapse">
<tr><td style="padding:4px 16px 4px 0;color:#94a3b8">Branch</td>
<td>${{ github.ref_name }}</td></tr>
<tr><td style="padding:4px 16px 4px 0;color:#94a3b8">Commit</td>
<td><code>${{ github.sha }}</code></td></tr>
<tr><td style="padding:4px 16px 4px 0;color:#94a3b8">Duration</td>
<td>$($duration)s</td></tr>
</table>
<h2 style="color:#4ade80;margin-top:20px">Changelog</h2>
<ul>$($changelog -join '')</ul>
</body></html>
"@
nylas email send `
--to "engineering@company.com" `
--subject "Deployed to ${{ inputs.environment }}: ${{ github.repository }}" `
--body $html `
--yesAzure DevOps: test failure notification
Azure DevOps pipelines support PowerShell natively with PowerShell@2 tasks. This pipeline sends a notification on test failure with parsed .trx results.
# azure-pipelines.yml
trigger:
branches:
include: [main]
pool:
vmImage: "windows-latest"
variables:
- group: nylas-secrets # Contains NYLAS_API_KEY
steps:
- task: PowerShell@2
displayName: "Install Nylas CLI"
inputs:
targetType: inline
script: irm https://cli.nylas.com/install.ps1 | iex
- task: DotNetCoreCLI@2
displayName: "Run tests"
inputs:
command: test
arguments: '--logger "trx;LogFileName=results.trx"'
continueOnError: true
- task: PowerShell@2
displayName: "Send test failure report"
condition: failed()
inputs:
targetType: inline
script: |
nylas auth config --api-key $(NYLAS_API_KEY)
# Parse .trx test results
$trxFile = Get-ChildItem -Recurse -Filter "results.trx" |
Select-Object -First 1 -ExpandProperty FullName
[xml]$trx = Get-Content $trxFile
$total = $trx.TestRun.ResultSummary.Counters.total
$passed = $trx.TestRun.ResultSummary.Counters.passed
$failed = $trx.TestRun.ResultSummary.Counters.failed
$failures = $trx.TestRun.Results.UnitTestResult |
Where-Object { $_.outcome -eq "Failed" } |
ForEach-Object { "- $($_.testName): $($_.Output.ErrorInfo.Message)" }
$body = @"
Build $(Build.BuildNumber) FAILED on $(Build.SourceBranch)
Test results: $passed/$total passed, $failed failed
Failed tests:
$($failures -join [Environment]::NewLine)
Pipeline: $(System.TeamFoundationCollectionUri)$(System.TeamProject)/_build/results?buildId=$(Build.BuildId)
"@
nylas email send --to "team@company.com" `
--subject "FAILED: $(Build.DefinitionName) #$(Build.BuildNumber)" `
--body $body --yes
env:
NYLAS_API_KEY: $(NYLAS_API_KEY)Jenkins: Jenkinsfile with email alerts
Jenkins pipelines call Nylas CLI from PowerShell steps. Store the API key in Jenkins Credentials.
// Jenkinsfile
pipeline {
agent any
environment {
NYLAS_API_KEY = credentials('nylas-api-key')
}
stages {
stage('Setup') {
steps {
sh 'curl -fsSL https://cli.nylas.com/install.sh | bash'
sh 'nylas auth config --api-key $NYLAS_API_KEY'
}
}
stage('Build') {
steps { sh 'make build' }
}
stage('Test') {
steps { sh 'make test' }
post {
failure {
pwsh """
\$body = @"
Build failed in stage: Test
Job: \$env:JOB_NAME
Build: #\$env:BUILD_NUMBER
Branch: \$env:GIT_BRANCH
URL: \$env:BUILD_URL
"@
nylas email send --to 'team@company.com' `
--subject "Jenkins FAILED: \$env:JOB_NAME #\$env:BUILD_NUMBER" `
--body \$body --yes
"""
}
}
}
}
post {
success {
pwsh """
nylas email send --to 'team@company.com' `
--subject "Jenkins PASSED: \$env:JOB_NAME #\$env:BUILD_NUMBER" `
--body "All stages passed. Build: \$env:BUILD_URL" `
--yes
"""
}
}
}Parse test results into email summaries
Different test frameworks output different formats. Here are PowerShell parsers for JUnit XML and Pester results.
# Parse JUnit XML (pytest, Go, Java)
function Get-JUnitSummary {
param([string]$Path)
[xml]$xml = Get-Content $Path
$suites = $xml.testsuites.testsuite
$total = ($suites | Measure-Object -Property tests -Sum).Sum
$failures = ($suites | Measure-Object -Property failures -Sum).Sum
$failedTests = $suites.testcase |
Where-Object { $_.failure } |
ForEach-Object { "- $($_.classname).$($_.name)" }
return @{ Total = $total; Failed = $failures; Details = $failedTests -join "`n" }
}
# Parse Pester XML
function Get-PesterSummary {
param([string]$Path)
[xml]$xml = Get-Content $Path
$r = $xml.'test-results'
$failedTests = $r.'test-suite'.results.'test-case' |
Where-Object { $_.result -eq 'Failure' } |
ForEach-Object { "- $($_.name)" }
return @{ Total = $r.total; Failed = $r.failures; Details = $failedTests -join "`n" }
}
# Use in any pipeline
$summary = Get-JUnitSummary -Path "./test-results.xml"
if ($summary.Failed -gt 0) {
nylas email send --to "team@company.com" `
--subject "Test failures: $($summary.Failed)/$($summary.Total)" `
--body $summary.Details --yes
}Smart notification patterns
Don't spam your team on every build. Target notifications to the right people at the right time.
# Pattern 1: Only notify on first failure (not repeated failures)
$lastStatus = if (Test-Path "./last-build-status.txt") {
Get-Content "./last-build-status.txt"
} else { "success" }
$currentStatus = "failure" # from your build step
if ($currentStatus -eq "failure" -and $lastStatus -eq "success") {
nylas email send --to "team@company.com" `
--subject "Build broke! Was passing, now failing." `
--body "First failure after a passing streak. Investigate." --yes
}
$currentStatus | Out-File "./last-build-status.txt"
# Pattern 2: Notify the commit author (not the whole team)
$authorEmail = git log -1 --format='%ae'
nylas email send --to $authorEmail `
--subject "Your commit broke the build" `
--body "Commit $(git log -1 --format='%h'): $(git log -1 --format='%s')" --yes
# Pattern 3: Escalate after 3+ consecutive failures
$failCount = if (Test-Path "./fail-count.txt") {
[int](Get-Content "./fail-count.txt")
} else { 0 }
$failCount++
$failCount | Out-File "./fail-count.txt"
if ($failCount -ge 3) {
nylas email send --to "engineering-lead@company.com" `
--subject "Build broken for $failCount consecutive runs" `
--body "Nobody has fixed the build. Escalating." --yes
}Automated release notes email
After tagging a release, generate notes from git history and email them to stakeholders.
# release-notes-email.ps1 -- Run after git tag
$currentTag = git describe --tags --exact-match 2>$null
$previousTag = git describe --tags --abbrev=0 HEAD~1 2>$null
if (-not $currentTag) { Write-Host "Not on a tag."; exit 0 }
# Group commits by type
$commits = git log --oneline "$previousTag..$currentTag"
$features = $commits | Where-Object { $_ -match "^[a-f0-9]+ (feat|add)" }
$fixes = $commits | Where-Object { $_ -match "^[a-f0-9]+ fix" }
$body = @"
Release $currentTag ($(Get-Date -Format 'yyyy-MM-dd'))
Previous: $previousTag
Features ($($features.Count)):
$($features | ForEach-Object { " $_" } | Out-String)
Fixes ($($fixes.Count)):
$($fixes | ForEach-Object { " $_" } | Out-String)
"@
nylas email send --to "stakeholders@company.com" `
--subject "Release $currentTag published" `
--body $body --yesFrequently asked questions
How do I send email notifications from GitHub Actions?
Install Nylas CLI with curl -fsSL https://cli.nylas.com/install.sh | bash in a setup step. Store your API key as a repository secret. Then call nylas email send from a pwsh step. No SMTP relay or email action plugin needed.
Can I send deployment reports from Azure DevOps pipelines?
Yes. Use a PowerShell@2 task that builds a report from pipeline variables like $(Build.BuildNumber) and $(Build.SourceBranch), then sends it with nylas email send. Store NYLAS_API_KEY in a variable group.
How do I send test failure alerts from CI/CD?
Parse your test results (JUnit XML, Pester, .trx) in a PowerShell step. Extract failing test names and error messages. Send a summary with Nylas CLI. Run this step conditionally: if: failure() in GitHub Actions, condition: failed() in Azure DevOps.
Next steps
- Server monitoring alerts with PowerShell -- disk space, service health, Event Log alerts
- Automated email reports with PowerShell -- CSV attachments, HTML tables, weekly digests
- Send email from PowerShell -- the foundation guide for PowerShell email
- Full command reference -- every flag and subcommand