Back to Blog
DevOps
4 min read

Sending Emails from Azure DevOps Pipelines

Azure DevOpsEmailAutomationSendGridMicrosoft Graph

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

  1. Create a SendGrid account
  2. Create an API key with Mail Send permission
  3. 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

  1. Register an app in Azure AD
  2. Grant Mail.Send application permission
  3. Admin consent the permission
  4. 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.

Need help with your Azure environment?

Get in touch for a free consultation.

Get in Touch