Dynamics 365 Customer Service ALM
Building a Dynamics 365 Customer Service ALM Project with C# Plug-ins, Power Automate and Azure DevOps
I recently built a hands-on Dynamics 365 Customer Service project to practise the complete development and Application Lifecycle Management process — from Dataverse customisation and C# plug-in development to Power Automate, external API integration, unit testing and automated deployment using Azure DevOps.
Instead of focusing only on configuration, my goal was to understand how the different parts of a real Dynamics 365 solution work together across development and deployment environments.
The project is available on GitHub:
GitHub: https://github.com/sangeetha2402-ravichandran/Dynamics365-CustomerService-ALM
The main areas I wanted to cover were:
Dynamics 365 Customer Service customisation
Microsoft Dataverse
C# plug-in development
Plug-in execution pipeline
Pre Images
Custom Workflow Activities
Power Automate
Email-to-Case automation
External REST API integration
Retry and error handling
Environment Variables
Connection References
Managed and unmanaged solutions
Unit testing
Service Principal authentication
Azure DevOps CI/CD
DEV → TEST deployment
Patch, Update and Upgrade strategies
High-Level Architecture
The project follows this general flow:
Dynamics 365 Customer Service
|
+-- Microsoft Dataverse
|
+-- C# Plug-ins
| |
| +-- Case creation logic
| +-- Priority handling
| +-- Follow-up task creation
| +-- External API integration
|
+-- Power Automate
| |
| +-- Case escalation
| +-- Incoming Email -> Case
| +-- Approval process
| +-- External API integration
|
+-- Environment Variables
+-- Connection References
|
v
Azure DevOps Pipeline
|
+-- Build
+-- Unit Test
+-- Export Managed Solution
+-- Manual Approval
+-- Deploy to TEST
C# Plug-in Development
I created multiple Dataverse plug-ins to understand different stages of the plug-in execution pipeline.
Case Creation Plug-in
The first plug-in runs when a Case is created.
I used it mainly to practise:
IPluginIPluginExecutionContextTarget Entity
PreOperation
Synchronous execution
Updating fields before Dataverse creates the record
Using PreOperation means I can modify the Target entity before Dataverse saves it, without making another update request afterwards.
Detecting Priority Changes Using a Pre Image
Another scenario was detecting when the Case priority changes from Normal to High.
For this, I registered a Pre Image containing the previous Case values.
The basic logic is:
Previous Priority = Normal
|
v
New Priority = High
|
v
Execute business logic
This helped me understand an important Dataverse concept: during an Update operation, the Target does not necessarily contain every attribute on the record.
The Pre Image allows the plug-in to compare the previous value with the new value.
Creating a Follow-up Task
I also created a PostOperation plug-in which creates a Task when a Case becomes High priority.
The generated task contains information such as:
Subject: Follow up high priority case
Regarding: Current Case
Priority: High
Due Date: +1 day
This scenario helped me practise:
IOrganizationServiceCreating Dataverse records
Entity references
Regarding lookups
Owner assignment
PostOperation execution
External REST API Integration
Another plug-in calls an external REST API when a Case is created.
I used HttpClient, processed the JSON response and updated the Case with information returned from the API.
For this type of integration, I also looked at:
Asynchronous plug-in execution
API timeout handling
External service failures
JSON response processing
For enterprise implementations, I would carefully consider whether the external integration should run directly from a plug-in or asynchronously through services such as Azure Functions or messaging, depending on reliability, latency and business requirements.
Power Automate — High Priority Case Escalation
I created a Power Automate flow for Case escalation.
The flow works approximately like this:
Case Priority becomes High
|
v
Create Follow-up Task
|
v
Wait
|
v
Read latest Case
|
v
Still High + Active + Not Escalated?
|
Yes
|
+--> Send escalation notification
|
+--> Mark Case as escalated
One thing I wanted to avoid was assuming that the Case was still High priority after waiting.
Therefore, the flow retrieves the latest Case information before performing the escalation.
That makes the automation safer when the Case has already been resolved or its priority has changed.
Email to Existing Case or New Case
I also created an email-driven Case flow.
When an email arrives, the process:
Incoming Email
|
v
Find Contact using sender email
|
+---- Contact Found
| |
| v
| Find Active Case
| |
| +---- Case Found -> Add Note
| |
| +---- No Case -> Create Case
|
+---- Contact Not Found -> Send Notification
This scenario gave me practical experience with:
Outlook triggers
Dataverse
List rowsOData filters
first()length()Contact lookups
Case lookups
Notes / annotations
Email-driven Case processing
Approval and Error Handling
For High priority Cases, I also created an approval process.
I used Scopes to follow a structure similar to:
TRY
|
+-- Start and wait for approval
|
+-- Process Approve / Reject
CATCH
|
+-- Handle failure or timeout
|
+-- Send failure notification
FINALLY
|
+-- Complete final processing
Power Automate's Configure run after functionality was used to control the Catch and Finally behaviour.
This helped me understand how workflows can be designed with failure scenarios in mind instead of handling only the successful path.
Environment Variables
I did not want environment-specific URLs hard-coded directly inside the solution.
For example:
DEV -> DEV API URL
TEST -> TEST API URL
PROD -> PROD API URL
An Environment Variable allows the same solution component to move between environments while receiving the correct configuration value for each environment.
This is especially useful when implementing proper Power Platform ALM.
Connection References
I used solution-aware Connection References for components such as:
Dataverse
Office 365 Outlook
Approvals
Conceptually:
Power Automate Flow
|
v
Connection Reference
|
v
Environment-specific Connection
This prevents a solution-aware flow from being tightly coupled to the developer's original connection when it is transported to another environment.
Unit Testing Dataverse Plug-ins
I created a separate MSTest project for plug-in testing.
For the High Priority Case plug-in, I mocked components such as:
IPluginExecutionContextITracingServiceIServiceProvider
using Moq.
One test scenario was:
Old Priority = Normal
New Priority = High
The expected plug-in behaviour is then verified without needing to manually perform the operation inside Dynamics 365 every time.
Adding unit tests into the repository also means they can be executed automatically by the CI pipeline.
Azure DevOps CI/CD
The next part of the project was automating ALM using an Azure DevOps YAML pipeline.
I organised the pipeline into several stages.
Stage 1 — Continuous Integration
NuGet Restore
|
v
Build Plug-in + Test Project
|
v
Run Unit Tests
|
v
Publish Build Artifact
If the build or tests fail, deployment should not continue.
Stage 2 — Export Solution from DEV
The pipeline connects to the DEV Power Platform environment and:
Publish Customisations
|
v
Export Managed Solution
|
v
Publish Solution ZIP as Artifact
The managed solution becomes the deployment artifact used for the TEST environment.
Manual Approval Before TEST
I added a manual approval stage between solution export and TEST deployment.
Build + Export
|
v
Manual Approval
|
+---- Approve -> Deploy
|
+---- Reject -> Stop
Although this is a learning project, I wanted the pipeline to represent the type of controlled release process used in enterprise environments.
Deploying to TEST
After approval, the managed solution is imported into the TEST environment using a Power Platform service connection.
I also created a deployment settings file for environment-specific configuration such as:
Environment Variable values
Connection Reference mappings
This separates environment configuration from the solution itself.
Patch vs Update vs Upgrade
Another important area I practised was Power Platform solution versioning.
Patch
A Patch is useful for a small targeted change.
Base Solution
+
Patch
Update
An Update deploys a newer version of an existing managed solution while retaining components that may not be present in the newer package.
Upgrade
An Upgrade can fully replace the previous solution layer and remove components that are no longer required.
A typical upgrade sequence is:
Import as Holding Solution
|
v
Apply Solution Upgrade
Understanding these differences is important when maintaining Power Platform applications over multiple releases.
A Useful ALM Issue I Faced
One of the most useful lessons came from a deployment failure.
The Azure DevOps Service Principal could authenticate successfully with Dataverse, but the pipeline failed with a connection authorization error.
The situation was similar to:
Pipeline Service Principal
|
v
Connection Reference
|
v
User-owned Connection
|
X
Service Principal cannot use connection
The deployment identity needed permission to use the target environment connection.
After sharing the required connection and providing the appropriate permission, the deployment was able to proceed.
This was a useful reminder that authentication to Dataverse and authorization to use a Power Platform connection are different concerns.
It also reinforced why enterprise environments should carefully manage service and integration accounts instead of depending on individual developer-owned connections.
Security
Because the repository is public, I made sure the project does not intentionally contain credentials such as:
Client Secrets
Passwords
Private Certificates
PFX files
Production Credentials
Personal Access Tokens
Secrets and environment-specific credentials should be stored securely and injected into deployment processes rather than committed to source control.
What I Learned
Building this project helped connect several topics that are often learned separately:
Dynamics development
C# plug-ins
Pipeline stages
Pre Images
Dataverse operations
Custom Workflow Activities
Power Platform
Power Automate
Environment Variables
Connection References
Solution-aware components
Integration
REST APIs
JSON
Retry handling
Failure handling
Engineering practices
Unit testing
Source control
CI/CD
Deployment approvals
ALM
Managed solutions
DEV → TEST deployments
Deployment settings
Patch
Update
Upgrade
Service Principals
The biggest benefit for me was seeing how these pieces work together as one lifecycle rather than treating Dynamics 365 configuration, development and deployment as separate topics.
Source Code
The complete project, including C# plug-ins, tests, Power Platform solution files, deployment settings and Azure DevOps YAML pipeline, is available here:
👉 https://github.com/sangeetha2402-ravichandran/Dynamics365-CustomerService-ALM