wiki.getshifting.com

--- Sjoerd Hooft's InFormation Technology ---

User Tools

Site Tools


azuredevopsextensionsendemailgraph

Azure DevOps Extension: Send email through Graph

Summary: How to get started with the Azure DevOps Extension for sending email through Microsoft Graph.
Date: 9 December 2025

This is a support page for the Azure DevOps Extension: Send email through Graph.
Marketplace link: https://marketplace.visualstudio.com/items?itemName=GetShifting.GraphEmail
GitHub Repository: https://github.com/getshifting/getshifting/tree/main/adoExtensionGraphEmail

Overview

The marketplace extension page provides a brief overview on how to use the extension, and how to configure the requirements. This page provides more examlples and also some background on how the extension creation is done and tested.

Yaml Examples

The examples below are also provided in the marketplace extension page:

This is an example of a task to send a multiline email:

- task: GetShifting.GraphEmail.graph-email-build-task.GraphEmail@0
  displayName: "Send an email with subject Test mail from $(BUILD.DEFINITIONNAME)"
  inputs:
    To: "sjoerd@getshifting.com"
    BCC: "$(BUILD.REQUESTEDFOREMAIL)"
    From: "sjoerd@getshifting.com"
    Subject: "Test mail from $(BUILD.DEFINITIONNAME)"
    Body: |
      <h1>This is a testmail</h1>
      <p>You can use various variables within the body of the email, for example:</p>
 
      <ul>
      <li> Build ID: $(build.buildid) </li>
      <li> Build Directory: $(agent.builddirectory) </li>
      <li> Build Queued By: $(BUILD.QUEUEDBY) </li>
      <li> Agent Name: $(AGENT.NAME) </li>
      <li> Job Name: $(SYSTEM.JOBDISPLAYNAME) </li>
      <li> Task Name: $(SYSTEM.TASKDISPLAYNAME) </li>
      <li> Build Requested for: $(BUILD.REQUESTEDFOR) </li>
      <li> Commit Message: $(BUILD.SOURCEVERSIONMESSAGE) </li>
      </ul>
 
      Kind regards, <br>
      $(BUILD.REQUESTEDFOR)
    ClientID: "e5e6ce84-d241-4faf-97e0-d71a171f1adf"
    ClientSecret: "$(ClientSecret)"
    ShowClientSecret: false
    TenantDomain: getshifting.com

This is an example of a task to send a single line email:

- task: GetShifting.GraphEmail.graph-email-build-task.GraphEmail@0
  displayName: "Send graph email with subject Testmail from $(BUILD.DEFINITIONNAME)"
  inputs:
    To: "sjoerd@getshifting.com"
    From: "sjoerd@getshifting.com"
    Subject: "Testmail from $(BUILD.DEFINITIONNAME)"
    Body: This is a short testmail
    ClientID: "$(ClientID)"
    ClientSecret: "$(ClientSecret)"
    TenantDomain: getshifting.com

Classic Pipeline Example

Even though classic pipelines are not used that much anymore, when I originally created the extension I still mostly used classic pipelines. Below is an example I used in my pipeline to test the extension:

An example of a task to send a multiline email


The Build and Release Pipeline

Below is the almost full build and release pipeline I use to build, test and release the extension to the marketplace. Some notes have been removed, and some of the guids are changed. The tokenized files that are used in the replacetokens task can be reviewed on the public repository of the extension: https://github.com/getshifting/getshifting/tree/main/adoExtensionGraphEmail:

name: $(Build.DefinitionName)-$(Build.BuildId)
appendCommitMessageToRunName: false

variables:
  - group: ExtensionGraph
  - name: ClientID
    value: "42ff8d60-02e0-43dd-9b57-008cbdd86dd2"
  - name: tfxcli
    value: "v0.9.x"
  - name: extensionid
    value: "graphemail"
  - name: publisherprod
    value: "getshifting"
  - name: publisher
    value: "getshifting-private"
  - name: public
    value: "false"
  - name: currentDate
    value: $[format('{0:yyyyMMdd}', pipeline.startTime)]

parameters:
  - name: action
    displayName: Action
    type: string
    default: "Build and Release Privately only"
    values:
      - "Build Only"
      - "Build and Release Privately only"
      - "Build and Release Public"

pool:
  vmImage: windows-latest

trigger: none

resources:
  repositories:
    - repository: self

stages:
  - stage: build
    displayName: "Stage: Build"

    jobs:
      - job: build
        displayName: "Job: Build & Package"
        steps:
          - task: PowerShell@2
            displayName: "Get System Variables"
            condition: eq(variables['System.debug'], true)
            inputs:
              pwsh: true
              targetType: "inline"
              script: |
                Write-Host "`n##[section]Get System Variables`n"
                Get-ChildItem -path env:* | Sort-Object Name

          - task: ms-devlabs.vsts-developer-tools-build-tasks.tfx-installer-build-task.TfxInstaller@5
            displayName: "Use Node CLI for Azure DevOps (tfx-cli): $(tfxcli)"
            inputs:
              version: "$(tfxcli)"

          - task: qetza.replacetokens.replacetokens-task.replacetokens@6
            displayName: 'Replace tokens in vss-extension.json graphEmail\task.json'
            inputs:
              sources: |
                vss-extension.json
                graphEmail\task.json
              tokenPattern: doubleunderscores
              telemetryOptout: true
              root: '$(System.DefaultWorkingDirectory)\public\adoExtensionGraphEmail'
              verbosity: "debug"

          - task: PowerShell@2
            displayName: "Add VstsTaskSdk PowerShell Module to the extension package"
            inputs:
              pwsh: true
              targetType: "inline"
              workingDirectory: '$(System.DefaultWorkingDirectory)\public\adoExtensionGraphEmail'
              script: |
                # Set the verbose flag based on pipeline variable
                if ($env:SYSTEM_DEBUG -eq "True"){
                  Write-Host "##[debug]Verbose logging is enabled"
                  $verboseFlag = $true
                } else {
                  $verboseFlag = $false
                }
 
                Write-Host "##[section]Set Variables"
                $vstsTaskSdkDir = ".\graphEmail\ps_modules\VstsTaskSdk"
                $vstsTempDir = ".\vststemp"
                Write-Host "VstsTaskSdk Directory     : $vstsTaskSdkDir"
                Write-Host "VstsTaskSdk Temp Directory: $vstsTempDir"
 
                if ($verboseFlag) {
                  Write-Host "##[debug]Check current directory structure"
                  Get-Location
                  Get-ChildItem -Recurse
                }
 
                Write-Host "##[section]Save VstsTaskSdk to $vstsTempDir and copy to $vstsTaskSdkDir"
 
                Write-Host "`nCreate required directory structure"
                New-Item -Path $vstsTaskSdkDir -ItemType "Directory" -Verbose:$verboseFlag
                New-Item -Path $vstsTempDir -ItemType "Directory" -Verbose:$verboseFlag
 
                Write-Host "`nSave VstsTaskSdk Module to temporary directory"
                Save-Module -Name VstsTaskSdk -Path $vstsTempDir -Verbose:$verboseFlag
 
                Write-Host "`nCopy all required files to VstsTaskSdk directory"
                Get-ChildItem -Path $vstsTempDir -Recurse -File -Depth 2 | ForEach-Object { Copy-Item -Path $_.FullName -Destination $vstsTaskSdkDir -Verbose:$verboseFlag }
 
                if ($verboseFlag) {
                  Write-Host "##[debug]Check current directory structure"
                  Get-Location
                  Get-ChildItem -Recurse
                }
 
                Write-Host "##[section]Remove temporary directory"
                Remove-Item -Path $vstsTempDir -Recurse -Force -Verbose:$verboseFlag
 
                if ($verboseFlag) {
                  Write-Host "##[debug]Check current directory structure"
                  Get-Location
                  Get-ChildItem -Recurse
                }

          - task: ms-devlabs.vsts-developer-tools-build-tasks.package-extension-build-task.PackageAzureDevOpsExtension@5
            displayName: "Package Extension"
            inputs:
              rootFolder: "$(Build.SourcesDirectory)/public/adoExtensionGraphEmail"
              outputPath: "$(build.artifactstagingdirectory)"
              extensionVersion: "$(currentDate).$(Build.BuildId).$(System.StageAttempt)"

          - task: PublishBuildArtifacts@1
            displayName: "Publish Artifact: extension"
            inputs:
              ArtifactName: extension

  - stage: releasePrivate
    displayName: "Stage: Release Private"
    condition: and(succeeded(),
      or(
      contains('${{ parameters.action }}', 'Build and Release Privately only'),
      contains('${{ parameters.action }}', 'Build and Release Public')))

    jobs:
      - job: releasePrivate
        displayName: "Job: Release Privately"
        steps:
          - task: ms-devlabs.vsts-developer-tools-build-tasks.tfx-installer-build-task.TfxInstaller@5
            displayName: "Use Node CLI for Azure DevOps (tfx-cli): $(tfxcli)"
            inputs:
              version: "$(tfxcli)"

          - task: DownloadPipelineArtifact@2
            inputs:
              artifactName: "extension"
              targetPath: "$(System.DefaultWorkingDirectory)/DevOpsExtensionGraph/extension"

          - task: ms-devlabs.vsts-developer-tools-build-tasks.publish-extension-build-task.PublishAzureDevOpsExtension@5
            displayName: "Publish Extension"
            inputs:
              connectTo: "VsTeam"
              connectedServiceName: "AzureDevOpsMarketPlace"
              fileType: vsix
              vsixFile: "$(System.DefaultWorkingDirectory)/DevOpsExtensionGraph/extension/getshifting-private.GraphEmail-$(currentDate).$(Build.BuildId).$(System.StageAttempt).vsix"
              updateTasksVersion: false
              extensionVisibility: private
              extensionPricing: free

      - job: waitForInstall
        displayName: "Job: Wait for the Private Extension to be installed"
        dependsOn: releasePrivate
        pool: server
        timeoutInMinutes: 1440
        steps:
          - task: ManualValidation@0
            displayName: "Wait for the private extension is installed"
            timeoutInMinutes: 120
            inputs:
              notifyUsers: |
                sjoerd@getshifting.com
              instructions: 'Go to the Marketplace icon in the top right, and select "Manage Extensions". Click on "Send email through Graph by GetShifting-Private" and wait for the extension to be automatically updated to the just released version.'
              onTimeout: "reject"

      - job: testPublic
        displayName: "Job: Test Privately"
        dependsOn: waitForInstall
        steps:
          - task: GetShifting-Private.GraphEmail.graph-email-build-task.GraphEmail@0
            displayName: "Send an email with subject Test mail from $(BUILD.DEFINITIONNAME)"
            inputs:
              To: "sjoerd@getshifting.com"
              BCC: "$(BUILD.REQUESTEDFOREMAIL)"
              From: "sjoerd@getshifting.com"
              Subject: "Test mail from $(BUILD.DEFINITIONNAME)"
              Body: |
                <h1>This is a testmail</h1>
                <p>You can use various variables within the body of the email, for example:</p>
 
                <ul>
                <li> Build ID: $(build.buildid) </li>
                <li> Build Directory: $(agent.builddirectory) </li>
                <li> Build Queued By: $(BUILD.QUEUEDBY) </li>
                <li> Agent Name: $(AGENT.NAME) </li>
                <li> Job Name: $(SYSTEM.JOBDISPLAYNAME) </li>
                <li> Task Name: $(SYSTEM.TASKDISPLAYNAME) </li>
                <li> Build Requested for: $(BUILD.REQUESTEDFOR) </li>
                <li> Commit Message: $(BUILD.SOURCEVERSIONMESSAGE) </li>
                </ul>
 
                Kind regards, <br>
                $(BUILD.REQUESTEDFOR)
              ClientID: "$(ClientID)"
              ClientSecret: "$(ClientSecret)"
              ShowClientSecret: true
              TenantDomain: getshifting.com

  - stage: releasePublic
    displayName: "Stage: Release Public"
    condition: and(succeeded(), eq('${{ parameters.action }}', 'Build and Release Public'))

    jobs:
      - job: releasePublic
        displayName: "Job: Release Publicly"
        steps:
          - task: ms-devlabs.vsts-developer-tools-build-tasks.tfx-installer-build-task.TfxInstaller@5
            displayName: "Use Node CLI for Azure DevOps (tfx-cli): $(tfxcli)"
            inputs:
              version: "$(tfxcli)"

          - task: DownloadPipelineArtifact@2
            inputs:
              artifactName: "extension"
              targetPath: "$(System.DefaultWorkingDirectory)/DevOpsExtensionGraph/extension"

          - task: ms-devlabs.vsts-developer-tools-build-tasks.publish-extension-build-task.PublishAzureDevOpsExtension@5
            displayName: "Publish Extension"
            inputs:
              connectTo: "VsTeam"
              connectedServiceName: "AzureDevOpsMarketPlace"
              fileType: vsix
              vsixFile: "$(System.DefaultWorkingDirectory)/DevOpsExtensionGraph/extension/getshifting-private.GraphEmail-$(currentDate).$(Build.BuildId).$(System.StageAttempt).vsix"
              publisherId: "$(publisherprod)"
              updateTasksVersion: false
              updateTasksId: true
              extensionVisibility: public
              extensionPricing: free

      - job: waitForInstall
        displayName: "Job: Wait for the Public Extension to be installed"
        dependsOn: releasePublic
        pool: server
        timeoutInMinutes: 1440
        steps:
          - task: ManualValidation@0
            displayName: "Wait for the public extension is installed"
            timeoutInMinutes: 120
            inputs:
              notifyUsers: |
                sjoerd@getshifting.com
              instructions: 'Go to the Marketplace icon in the top right, and select "Manage Extensions". Click on "Send email through Graph by GetShifting" and wait for the extension to be automatically updated to the just released version.'
              onTimeout: "reject"

      - job: testPublic
        displayName: "Job: Test Publicly"
        dependsOn: waitForInstall
        steps:
          - task: GetShifting.GraphEmail.graph-email-build-task.GraphEmail@0
            displayName: "Send graph email with subject Testmail from $(BUILD.DEFINITIONNAME)"
            inputs:
              To: "sjoerd@getshifting.com"
              From: "sjoerd@getshifting.com"
              Subject: "Testmail from $(BUILD.DEFINITIONNAME)"
              Body: Test from public
              ClientID: "$(ClientID)"
              ClientSecret: "$(ClientSecret)"
              TenantDomain: getshifting.com

Useful Resources

This wiki has been made possible by:

azuredevopsextensionsendemailgraph.txt · Last modified: by sjoerd