How to Integrate Dynamics 365 (CRM) with Constant Contact

Many companies use a mix of Dynamics and other applications to handle their needs for CRM, customer outreach, and more.

In this article, I will show you how to integrate one or more Dynamics 365 CRM applications like Dynamics 365 Sales with the email management software Constant Contact, by building a Dataverse custom connector. I’ll also show you how to automatically update Constant Contact data based on your Dataverse data, using that connector.

For your specific configuration, you may need the help of a skilled Dynamics partner. However, the guidance here will give you a clearer sense of a viable approach that your internal developer or a Dynamics partner could take to connect Dynamics CRM applications to a non-Microsoft application.

Note: There are now 5 Dynamics solutions that fulfil different CRM needs. In some contexts, you may also see those products referred to as “Customer Engagement” or “CE” apps.  All of these are based on Dataverse, and the integration described here will work no matter which of these apps you are using.

Legend

As a helpful guide, I am providing a legend for used abbreviations:

  • CC = Constant Contact
  • CE = Customer Engagement
  • DV = Dataverse
  • PA = Power Automate

Situation

As of June 2022, a pre-built template or connector (with triggers and actions) does not exist between the Dataverse (on which Dynamics Customer Engagement apps are built) and Constant Contact. For this reason, a custom connector is required to integrate data between the two systems. If an integration is needed and a custom connector is not used, third-party tools would be required.

How to Build a Custom Connector Between Dataverse and Constant Contact

Here are some items that need to be in place before beginning:

  • Customer Engagement license for any of the five D365 Customer Engagement (aka CRM) applications
  • Dataverse environment
  • Power Automate per User Plan license (or equivalent; needs to use premium features)
  • Constant Contact license and account
  • Internal developer or Dynamics partner with Power Automate experience
  • Understanding of Constant Contact API

Custom Connector Between Dynamics 365 (Dataverse) and Constant Contact

Dynamics 365 CE solutions like Dynamics 365 Sales are built on the Dataverse, which in turn is built on the Power Platform. This allows you to extend the functionality of your Dynamics 365 solution to work with other systems, including non-Microsoft systems like Constant Contact. And since CC has a robust open API, we can follow Microsoft’s instructions on how to build a custom connector with an open API definition.

The first time you try this, it may not be a straightforward process, because it appears the DV custom connector requirements do not match up with CC’s API. We will walk through these obstacles, and how to overcome each of them, as we create the custom connector.

Constant Contact

Register Application

To begin, navigate and log into your Constant Contact account and register your CC application by following these instructions.

It is important that you register the application, step 2, as this generates the API Key that will be used by the Dataverse custom connector. Copy and paste the API Key and secret in Notepad for later use.

Do not complete step 3 at this time; this will happen after you create the custom connector in the Power Platform.

Download CC API

While still in Constant Contact, navigate here and download the API as a YAML file. Save the API file to a location where you can easily find and utilize it.

Dataverse

Import Open API

Log into your Dataverse, navigate to the correct environment, expand Dataverse in the left navigation and click on Custom Connectors. Then click New custom connector and then click Import an OpenAPI file.

Dropdown menu in Dataverse, highlighting the option to Import an OpenAPI file

Provide a name for the custom connector that will be visible throughout the Power Platform and then import the Constant Contact file.

And here is where you will face the first challenge. The import process is looking for a JSON file, but Constant Contact only provides a YAML file.

Import process screen from Dataverse, highlighting the requirement for a JSON file

We can see that the swagger.yaml file is not showing in the File Explorer.

Thankfully for us, there is a wonderful online tool that will convert YAML to JSON.

Follow their process, and then upload the new JSON file:

Import screen from Dataverse, showing the new JSON file selected

Enter General Information

Once the new JSON file is successfully uploaded, you will be redirected to the first step of the custom connector process within Dataverse.

5 steps of the connector process within Dataverse: general, security, definition, code, and test

Confirm the information and click the Security link at the bottom right.

Enter in all the following information:

Authentication Type

OAuth 2.0

Identity Provider

Generic Oauth 2

Client id (API Key)

This is generated when you register the Constant Contact application.

Client secret

This is generated when you register the Constant Contact application.

Authorization URL (Get requests)

https://authz.constantcontact.com/oauth2/default/v1/authorize

Token URL (Post)

https://authz.constantcontact.com/oauth2/default/v1/token

Refresh URL

https://authz.constantcontact.com/oauth2/default/v1/token

Scope

contact_data, campaign_data, account_read, account_update, offline_access

(These may need to be different, depending on what you will need to do with the custom connector.)

Review CC’s user privileges to determine which roles need to be added to scope:

https://v3.developer.constantcontact.com/api_guide/scopes.html

Security details screen

Create Connector

After you have entered all this information, click Create connector at the top.

Resolve Errors

This is where you will find a mismatch of system requirements between the Dataverse custom connector and Constant Contact’s API. You will receive this error:

Your custom connector has been successfully created, but there was an issue converting it to WADL for Power Apps: An error occurred while converting OpenAPI file to WADL file. Error: ‘Operation id “getAuthCode” must specify either a default response or a 200-series response. Any other responses in the swagger is not supported by PowerApps client at JSON path paths[‘/idfed’].get’

It will also say, “The request failed with client error.”

At the top of the custom connector, there is a Swagger Editor switch:

Swagger Editor switch within step 3 of the custom connector process

Switch it on.

Now you will see a list of errors like the below:

Swagger code shown within Swagger Editor

Click anywhere in the Swagger code and on your keyboard hit CTRL-F to search the code. Type in “getAuthCode” and hit Enter. You will see the below:

Output from getAuthCode, showing three lines with errors: oath2_implicit, oath2_access_code, and api_key

Pay particular attention to the responses. There is only a 302 response, and the error message is indicating that we need a 200-series response, so we will need to add one.

Note the line number of the code (3539).

Now search for “responses” in the code.

I found this:

3 code lines, showing a 200 response.

Copy and paste the 200 and the description line into Notepad and adjust to as follows:

‘200’: {description: Request Successful}

Now paste this into the Swagger code. Now the getAuthCode responses will look like this:

5 code lines, showing the new 200 response inserted

After clicking Update connector at the top, the connector saves successfully and then we notice a successful message at the top:

Custom connector success message

You will, however, see some further errors, for instance,
Semantic error at paths./account/user/privileges.get.security.0

These errors may have to do with the /account/user/privileges endpoint (oauth2_implicit, oath2_access_code and api_key).

However, these are only semantic errors, and they can be safely ignored in this case.

You can jump to the next step, Test Custom Connector.

Or to explore those errors see below. Navigate to the Definition step, if not already there, and you will see some of the errors:

Dataverse Definition step showing error messages

Without a doubt, the most common type of error listed below was a semantic error:

Error message: Semantic error at paths./account/user/privileges.get.security.0

Navigating to the mentioned line, we see:

3 error lines in the code: oath2_implicit, oath2_access_code, and api_key]

These error lines for oauth2_implicit, oath2_access_code and api_key represent the /account/user/privileges endpoint.

Open a new browser tab and navigate to Constant Contact’s user privileges info.

Here we can see the privileges for this endpoint:

Constant contact user privileges showing the endpoint method and route

However, this endpoint does not need any privileges.

What does this mean? The Constant Contact OpenAPI file is not meshing with the Dataverse custom connector requirements. But because it is only a semantic error with no immediate steps of reconciliation, you can continue. (Remember, the custom connector did save successfully with no major errors.)

Test Custom Connector

We need to test our custom connector to ensure everything is in order. Navigate to the Test step and click New connection.

Dataverse screen showing test step

The following dialog box will appear. Click Create.

Constant Contact API dialog box, with Create button

Enter your Constant Contact credentials.

Constant Contact sign-in screen

For me, the dialog box closed, and the system navigated me to the Connections page:

Connections page showing CCAPI connected

At this point, the custom connector is all done. Now we need to update CC.

Constant Contact – Update Redirect URL

Inside the DV, the Redirect URL will be generated after you first save the custom connector, and it will be the following: https://global.consent.azure-apim.net/redirect

Insert the DV-generated Redirect URL into Constant Contact’s registered application’s Redirect URL value and save.

Inserting the redirect URL into Constant Contact

Test in Power Automate

To see if the custom connector is available, create a new Power Automate; for testing purposes, it can be a recurrence flow.

Click New step, and in the search for Choose an operation, type in the name you provided your custom connector; in this case, I searched for “ccapi” and received the below results:

Insert the custom connector as operation in Power Automate

After selecting GET Contacts Collection, you will need to sign into Constant Contact one more time.

Constant Contact sign in through Power Automate

Once verifying the CC credentials, the action will appear:

GET details within Power Automate

Inside CC, find an email that is represented and copy and paste it into the email value above. Manually test the flow and view the results:

Flow results showing outputs from GET contacts

Success! It ran beautifully and returned the expected results from the CC endpoint.

How to Update Constant Contact Data with Dataverse Data

The following instructions will allow you to:

  • Update Dynamics 365 (Dataverse) when a contact in Constant Contact has been removed from a list.
  • Update CC when a DV contact’s information has changed.
  • Update CC when a DV contact’s communication preferences have changed.
  • Update CC when a DV contact has been removed from an active Marketing List in D365 Sales or Marketing.

The solution is not overly complex, but there are certain nuances that force us to follow a particular path you may not have originally expected. I’ll explain below.

Dynamics 365 to Constant Contact Flow

It may be important for your business that all Dynamics 365 Contacts that are on an active Marketing List must have matching data for each corresponding CC Contact, including the following D365 CE data points:

First Name, Last Name, Email, Job Title, and Account.

The Flow will determine if there are any differences, and if so, it will update the CC Contact.

In order to prevent sending bulk emails to Contacts who have opted out, all D365 Contacts that are on an active Marketing List but have Do Not Allow Bulk Emails set to true will need to be removed from the matching CC list.

The overall process will loop through all D365 active Marketing Lists and then retrieve each Member (CC Contact). The Flow will find a matching CC Contact on the matching list and determine if there are any data differences. If yes, the Flow will update the CC Contact. Then, if the CC Contact Do Not Allow Bulk Email is set to true, it will remove the CC Contact from the matching list.

One important item to note is that the D365 Marketing List is a dynamic list, meaning that there is a query that determines when a Contact is added to or removed from the list. Actually, this becomes quite useful as we build out the Flow.

Power Automate

When creating a new Power Automate inside the DV, the first item that must be identified, before navigating to the PA screen, is the trigger. You might have expected to use the “When a row is added, modified, or deleted” trigger in an attempt to have the data be updated in real time. However, certain limitations mean you should choose the recurrence-type Flow.

Log into your DV environment and create a new solution or open an existing solution. Click New > Automation > Cloud flow > Automated.

Power apps menu showing the path to choose Automated

Click Skip on the first dialog screen.

Close-up of Power automate buttons for skip, create, and cancel

On the next screen, search for “Recurrence” and select.

Power Automate interface showing the recurrence-schedule trigger

The Flow development page will appear. Set the recurrence settings as needed to meet your business requirements.

Power apps interface showing the options for recurrence settings

Initialize Variables

We need several variables as we are looping through the D365 CE Contacts: CC List Id, First, Last, Email, Job title, Organization (Account), and Is Difference Between CRM and CC Contact.

The first six are string, and the last one is a Boolean.

Actions for initializing 6 string variables: CC List Id, First, Last, Email, Job title, Organization (Account) Action for initializing a Boolean variable called Is Difference Between CRM and CC Contact

Get Active Marketing Lists

After we have initialized the variables above, we create a new action, List rows, to retrieve all active D365 Marketing Lists, using the Fetch XML Query option.

Action for Fetch XML Query

Fetch XML Query

<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">

  <entity name="list">

    <attribute name="listname" />

    <attribute name="type" />

    <attribute name="createdfromcode" />

    <attribute name="lastusedon" />

    <attribute name="purpose" />

    <attribute name="listid" />

    <order attribute="listname" descending="true" />

    <filter type="and">

      <condition attribute="statecode" operator="eq" value="0" />

    </filter>

  </entity>

</fetch>

Use CC List Id

To pair up D365 CE Marketing Lists to CC lists, I created a custom column on the D365 Marketing List table; the column name is Constant Contact List Id.

There is a bit of a manual set up for this; as each new CC list is created, a user will need to grab the CC GUID for the list and enter it into the D365 Marketing List column as seen below. This is critical in fusing the lists together between the two systems.

If desired, a new Power Automate could be designed to utilize the POST (create) a List CC API endpoint whenever a D365 Marketing List is created, but that would be another blog.

Column on D365 Marketing List
Column on D365 Marketing List, showing a Constant Contact List Id

 

Loop Through and Get Active Marketing Lists

We need to loop through all the retrieved active Marketing Lists, so create a new action, search for “apply to each,” and select.

Inside the loop, create a new action, search for “Get a row by ID,” and select. Enter the Marketing List Id in the Row ID value.

Action for looping through the table named Marketing Lists

Set CC List Id

To match the D365 Marketing list with the CC list, we will use the unique connecting Id to ensure we get the correct list on the CC side. Create a new action and search for “Set variable” and select and enter the Constant Contact List Id column in the Value input.

Action for setting the CC List Id

Get Contacts on Active Marketing List

To retrieve all the D365 Contacts on the D365 Marketing List, we use the List rows action and insert the Marketing List Query column for the Fetch XML Query. This is the query of the dynamic Marketing List that determines which Contacts are associated.

Marketing List Query

When opening any dynamic D365 Marketing List and then clicking on Manage Members at the top, you will view FetchXML like below:

D365 Marketing List Manage Members interface

Query Used in Power Automate

In the List Rows action to retrieve all Contacts that are associated with the active Marketing List, we can utilize that query column as the Fetch XML Query. Very cool!

Action for selecting the pre-existing Query as the Fetch Xml Query

Set Variables

We loop through the list of Contacts and set variables with Contact data to be used further down in the Flow.

First, we create a new action and search for “Get a row by ID” and select and then enter the Contact Id for the Row ID value.

Action for Get a row by ID within Loop through marketing list members

From there, we set each of the following variables with D365 Contact data: First, Last, Email, Job title, Organization.

Actions for setting variables within Loop through marketing list members

First name as an example of how to set these variables:
Action for setting the variable called First to the dynamics value of First Name

Get Matching Constant Contact

Here is our first use of the custom connector we developed above to connect the DV and CC. We use the GET Contacts Collection and limit it by email address. Ensure to select Yes for include_count.

Get contacts collection limited by email address Compose action for GET contacts collection

The include_count informs us if there are any results, so we create a Compose action to hold this value:

Then we create a Condition action to determine if any results were found; if there was one result, we continue (yes branch); if there were zero results, we create a contact on the CC side (right branch; not part of this blog).
Condition action depending on whether the output is equal to 1

We loop through the CC list by using another apply to each action and ensuring we enter the “contacts” value from the GET Contacts Collection results.

Action for Loop Through GET Contacts Collection

Determine if Data Has Changed

Let’s assume that the DV will be the master data store and all CC contact information should reflect DV data.

We create a Condition action and compare D365 values against CC values.

Condition checking if there are any differences between CC and D365 data

If there are any differences between D365 and CC data, we keep going.

Update Constant Contact

If there are any differences between D365 and CC data, we will update the CC Contact with the Create or Update a Contact endpoint.

Action for creating or updating a contact in Constant Contact

Flow Conclusion

Hopefully, the above steps provided enough information for you to create your own Flow that interacts with the DV and CC data. There are many different business requirements that could be met with the custom connector now in place. All that needs to happen is to have a clear understanding of the CC API and its limitations and what can be done with Power Automate.

Gotchas

There are many little obstacles and gems that you may find along the way in this process. Below is a brief list that may help you in your Power Automate development.

GET Contacts Collection Limit

Surprisingly, this CC API endpoint has a max limit of only 500. I am sure it is for performance reasons, but there is not much you can do with this endpoint unless you have significant criteria to limit the number of results. See the limitation below:

Screen showing the CC API endpoint limit

Retrieving all unsubscribed CC contacts and updating DV was one of my original ideas, however, this could not be done due to the 500 max limit. Therefore, I started with the DV and retrieved all Contacts on active Marketing Lists. By doing this, the Flow had access to the Contact’s email field which would limit the results to around one.

CC Contact Status

A Contact may exist in CC and may be associated to several lists. Their status may be “subscribed” even though they may not be on the list in which you are searching. My understanding is that a status of “unsubscribed” means they do not exist on any list. For this reason, a combination of data points is needed to retrieve the relevant CC data. You would not be able to retrieve CC contact information from a list in which they are not subscribed. This, potentially, changes which CC API datapoints are used.

GET a Contact

This API endpoint does not allow you to find a Contact by an email; the CC contact Id is required. You must use the GET Contacts Collection endpoint if the DV Contact email is known. With that said, and depending on the level of integration needed, you could create a custom column on the DV Contact table to store the CC contact Id and then use that value to use this endpoint.

Create or Update a Contact

In my opinion, it makes sense to use the Create or Update a Contact endpoint rather than the POST (create) a Contact and PUT (update) a Contact endpoints, so you only must build around one endpoint. This helps in the building of the PA and the number of CC API endpoints used.

GET a List and GET Lists Collection

You cannot get a CC list by the list name for either the GET a List or GET Lists Collection endpoints. I found this to be surprising, and honestly, disappointing. For this reason, I created a custom column in the DV to match the DV Marketing List and the CC list.

CE Marketing List Query

The best way to find out if a D365 Contact exists on a Marketing List is to utilize the Marketing List Query column. I have completely described this process above but have displayed the screenshots below for illustration purposes.

Get Active Marketing Lists

Action for Fetch XML Query

Loop Through and Get Active Marketing Lists

Action for looping through the table named Marketing Lists

Get Contact on Active Marketing List

Action for selecting the pre-existing Query as the Fetch Xml Query

Conclusion

This blog has demonstrated the steps necessary to integrate Constant Contact with Dynamics 365 (CRM) solutions via Power Automate. It also allows you to update CC data from D365 data.

If you’d like assistance and advice from a partner with experience in using Dynamics and the Power Platform together with other business applications, please contact us.

Webinar - End-to-End Resource Scheduling in Dynamics 365 Project Operations

This webinar provides an end-to-end overview of how you can schedule resources in D365 Project Operations. This overview will be useful to project managers, schedulers, and sys admins.

October 11
9:00 am – 9:30 am PST

Register Here

Webinar - D365 Project Operations Scheduling

Oct. 11
9:00 am – 9:30 am PST

Register