Azure DevOps has built-in notifications, but sometimes you need custom email content, specific recipients, or formatted reports. Here's how to send emails from your pipelines.
Option 1: SendGrid (Recommended)
Simple API, generous free tier (100 emails/day).
Setup
- Create a SendGrid account
- Create an API key with Mail Send permission
- Add as a pipeline variable or Key Vault secret
Pipeline Task
- task: PowerShell@2
displayName: 'Send Email via SendGrid'
inputs:
targetType: 'inline'
script: |
$sendGridApiKey = "$(SendGridApiKey)"
$body = @{
personalizations = @(
@{
to = @(
@{ email = "[email protected]"; name = "DevOps Team" }
)
subject = "Build $(Build.BuildNumber) Completed"
}
)
from = @{
email = "[email protected]"
name = "Azure DevOps"
}
content = @(
@{
type = "text/html"
value = @"
<h2>Build Completed</h2>
<p><strong>Pipeline:</strong> $(Build.DefinitionName)</p>
<p><strong>Build Number:</strong> $(Build.BuildNumber)</p>
<p><strong>Status:</strong> $(Agent.JobStatus)</p>
<p><strong>Branch:</strong> $(Build.SourceBranchName)</p>
<p><a href="$(System.TeamFoundationCollectionUri)$(System.TeamProject)/_build/results?buildId=$(Build.BuildId)">View Build</a></p>
"@
}
)
} | ConvertTo-Json -Depth 10
Invoke-RestMethod -Uri "https://api.sendgrid.com/v3/mail/send" `
-Method Post `
-Headers @{ "Authorization" = "Bearer $sendGridApiKey" } `
-ContentType "application/json" `
-Body $body
Option 2: Microsoft Graph API
Use existing Microsoft 365 infrastructure.
App Registration Setup
- Register an app in Azure AD
- Grant
Mail.Sendapplication permission - Admin consent the permission
- Create a client secret
Pipeline Task
- task: AzurePowerShell@5
displayName: 'Send Email via Graph'
inputs:
azureSubscription: 'your-service-connection'
ScriptType: 'InlineScript'
Inline: |
$tenantId = "$(TenantId)"
$clientId = "$(ClientId)"
$clientSecret = "$(ClientSecret)"
$senderEmail = "[email protected]"
# Get access token
$tokenBody = @{
grant_type = "client_credentials"
scope = "https://graph.microsoft.com/.default"
client_id = $clientId
client_secret = $clientSecret
}
$tokenResponse = Invoke-RestMethod `
-Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" `
-Method Post `
-Body $tokenBody
$accessToken = $tokenResponse.access_token
# Send email
$emailBody = @{
message = @{
subject = "Build $(Build.BuildNumber) - $(Agent.JobStatus)"
body = @{
contentType = "HTML"
content = @"
<h2>Pipeline: $(Build.DefinitionName)</h2>
<table>
<tr><td><strong>Build:</strong></td><td>$(Build.BuildNumber)</td></tr>
<tr><td><strong>Status:</strong></td><td>$(Agent.JobStatus)</td></tr>
<tr><td><strong>Branch:</strong></td><td>$(Build.SourceBranchName)</td></tr>
<tr><td><strong>Commit:</strong></td><td>$(Build.SourceVersion)</td></tr>
</table>
<p><a href="$(System.TeamFoundationCollectionUri)$(System.TeamProject)/_build/results?buildId=$(Build.BuildId)">View Build Results</a></p>
"@
}
toRecipients = @(
@{ emailAddress = @{ address = "[email protected]" } }
)
}
saveToSentItems = $false
} | ConvertTo-Json -Depth 10
Invoke-RestMethod `
-Uri "https://graph.microsoft.com/v1.0/users/$senderEmail/sendMail" `
-Method Post `
-Headers @{ "Authorization" = "Bearer $accessToken" } `
-ContentType "application/json" `
-Body $emailBody
azurePowerShellVersion: 'LatestVersion'
Option 3: SMTP with Azure Communication Services
- task: PowerShell@2
displayName: 'Send Email via SMTP'
inputs:
targetType: 'inline'
script: |
$smtpServer = "smtp.azurecomm.net"
$smtpPort = 587
$username = "$(AcsSmtpUsername)"
$password = "$(AcsSmtpPassword)"
$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($username, $securePassword)
$emailParams = @{
From = "DevOps <[email protected]>"
To = "[email protected]"
Subject = "Build $(Build.BuildNumber) Complete"
Body = "Build completed with status: $(Agent.JobStatus)"
SmtpServer = $smtpServer
Port = $smtpPort
UseSsl = $true
Credential = $credential
}
Send-MailMessage @emailParams
Conditional Emails
Only send on failure or specific conditions:
- task: PowerShell@2
displayName: 'Send Failure Notification'
condition: failed()
inputs:
targetType: 'inline'
script: |
# Send email only when build fails
$subject = "BUILD FAILED: $(Build.DefinitionName) #$(Build.BuildNumber)"
# ... email sending code
Attaching Build Artifacts
- task: PowerShell@2
displayName: 'Send Email with Attachments'
inputs:
targetType: 'inline'
script: |
$attachmentPath = "$(Build.ArtifactStagingDirectory)/report.pdf"
$attachmentBytes = [System.IO.File]::ReadAllBytes($attachmentPath)
$attachmentBase64 = [Convert]::ToBase64String($attachmentBytes)
$body = @{
personalizations = @(
@{
to = @(@{ email = "[email protected]" })
subject = "Build Report - $(Build.BuildNumber)"
}
)
from = @{ email = "[email protected]" }
content = @(
@{ type = "text/plain"; value = "Please see attached build report." }
)
attachments = @(
@{
content = $attachmentBase64
filename = "build-report.pdf"
type = "application/pdf"
}
)
} | ConvertTo-Json -Depth 10
Invoke-RestMethod -Uri "https://api.sendgrid.com/v3/mail/send" `
-Method Post `
-Headers @{ "Authorization" = "Bearer $(SendGridApiKey)" } `
-ContentType "application/json" `
-Body $body
Email Templates
Keep templates in your repo for consistency:
- task: PowerShell@2
inputs:
targetType: 'inline'
script: |
$template = Get-Content "$(Build.SourcesDirectory)/templates/build-notification.html" -Raw
# Replace placeholders
$body = $template `
-replace "{{BuildNumber}}", "$(Build.BuildNumber)" `
-replace "{{Status}}", "$(Agent.JobStatus)" `
-replace "{{Pipeline}}", "$(Build.DefinitionName)" `
-replace "{{Branch}}", "$(Build.SourceBranchName)"
# Send email with $body
Need help with DevOps automation? Get in touch - we help organisations build efficient CI/CD pipelines.