<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: rainforss</title>
    <description>The latest articles on DEV Community by rainforss (@rainforss).</description>
    <link>https://dev.to/rainforss</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F848607%2Fa0909109-d0f2-48f1-bf10-96bddcde49b7.png</url>
      <title>DEV Community: rainforss</title>
      <link>https://dev.to/rainforss</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rainforss"/>
    <language>en</language>
    <item>
      <title>How to add an icon to a D365 segment template</title>
      <dc:creator>rainforss</dc:creator>
      <pubDate>Mon, 28 Nov 2022 07:10:02 +0000</pubDate>
      <link>https://dev.to/rainforss/how-to-add-an-icon-to-a-d365-segment-template-2d55</link>
      <guid>https://dev.to/rainforss/how-to-add-an-icon-to-a-d365-segment-template-2d55</guid>
      <description>&lt;p&gt;&lt;em&gt;As a D365 Marketing user, have you ever wondered why segment templates you created do not have icons like the system provided segment templates?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdkfa1mjtbb3q6wuew0pv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdkfa1mjtbb3q6wuew0pv.png" alt="List view of segment templates" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;
Find the imposter!






&lt;p&gt;Although we can use filters, naming conventions and descriptions to locate the template we need, a visual hint is always good to have. Several clients and community members have asked about how to add custom icons to user created segment templates so I am going to demonstrate the simple steps to enable custom icons in this article.&lt;/p&gt;




&lt;p&gt;Conceptual aside - the icon image we see on a segment template record is stored in an "Image" column of "Segment Template" table, named "Icon". &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fal25li7rzs5s8ib4gbb7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fal25li7rzs5s8ib4gbb7.png" alt="Image column of segment template" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;
Image column of a table






&lt;p&gt;A table can have multiple image columns but only one can be dedicated as a "Primary Image Column". In a normal table (without further customization), the picture stored in the primary image column will be the icon shown on a record. The tables without a primary image column will not have this icon on the table form.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuygao1h1ihhcfgyyuh2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuygao1h1ihhcfgyyuh2w.png" alt="Contact form" width="573" height="175"&gt;&lt;/a&gt;&lt;/p&gt;
Primary image column shown on the main form






&lt;p&gt;On the segment template table, the image stored in primary image column will be shown directly on the segment templates list view, tile view and main form.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftruzmxybhgdp89px1ic0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftruzmxybhgdp89px1ic0.png" alt="Segment template list view" width="646" height="192"&gt;&lt;/a&gt;&lt;/p&gt;
List view






&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F01cs7v3l41w4e1xdqrs4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F01cs7v3l41w4e1xdqrs4.png" alt="Segment template tile view" width="445" height="408"&gt;&lt;/a&gt;&lt;/p&gt;
Tile view






&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4tlnt7s3j74taaamfpax.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4tlnt7s3j74taaamfpax.png" alt="Segment template main form" width="278" height="146"&gt;&lt;/a&gt;&lt;/p&gt;
Main form






&lt;p&gt;Now to the actual process. I mentioned steps, but it is actually &lt;strong&gt;one step&lt;/strong&gt; - simply left click on the icon on the main form and a dialog will pop up for you to view or edit the image. It is so simple that this step did not even cross my mind. Previously, I was under the assumption that the primary image column would have to be added to the main form for users to modify. However, &lt;a href="https://meganvwalker.com/" rel="noopener noreferrer"&gt;Megan&lt;/a&gt; told me about the simple approach which made me rethink about solutioning for any requirements or challenges - there always might be an out-of-box approach to look for before thinking about a customized solution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcn9ht9wcts0tpgjoxkh3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcn9ht9wcts0tpgjoxkh3.png" alt="Change the icon" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;
A simple click and you can change the icon






&lt;p&gt;Voila! Now you have the ability to add visuals to help other users understand what your segment template does. Just remember that any "Image" column data can only be populated after a record is saved, not when a record is being created.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Some additional words for the adventurous type of people: being able to add more visuals is fantastic, but the image column should be used sparingly since it will &lt;strong&gt;eat up the allocated file storage&lt;/strong&gt; within an environment if a lot of tables are configured to have image columns. Only use the image column when it is necessary and make sure you set a reasonable max image size when creating an image column. Otherwise, always use less expensive solutions such as &lt;strong&gt;&lt;a href="https://learn.microsoft.com/en-us/power-platform/admin/set-up-dynamics-365-online-to-use-sharepoint-online" rel="noopener noreferrer"&gt;SharePoint integration&lt;/a&gt;&lt;/strong&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1bzz7utb4fbmqzmdilxd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1bzz7utb4fbmqzmdilxd.png" alt="Image column configuration" width="351" height="603"&gt;&lt;/a&gt;&lt;br&gt;The maximum image size can only be configured when the column is being created, not afterwards
&lt;/p&gt;

&lt;p&gt;I hope that this article is somewhat useful to you and feel free to reach out if you have any questions. Happy marketing!&lt;/p&gt;

</description>
      <category>emptystring</category>
    </item>
    <item>
      <title>How to generate dynamic email signature when sending in D365 Marketing</title>
      <dc:creator>rainforss</dc:creator>
      <pubDate>Wed, 09 Nov 2022 01:16:36 +0000</pubDate>
      <link>https://dev.to/rainforss/how-to-generate-dynamic-email-signature-when-sending-in-d365-marketing-3155</link>
      <guid>https://dev.to/rainforss/how-to-generate-dynamic-email-signature-when-sending-in-d365-marketing-3155</guid>
      <description>&lt;p&gt;&lt;em&gt;When sending emails in D365 Marketing app, it is always a good practice to include some personalized content in the email for a special and lively touch.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Recently, a lot of clients have the requirement to take the personalization further and include an email signature of the salesperson (or marketing person). In my opinion, it is a wise move because a email signature can make the communication feel less "automated". However, do make sure that "reply-to" address points to an accessible email box in case a recipient replies to the email directly since you don't want to miss out on a customer's inquiry.&lt;/p&gt;

&lt;p&gt;The logic of this implementation is quite simple: when sending an email to a contact during a customer journey, append the email signature of the owning user for the contact record at the end of email body. If you are no stranger to &lt;strong&gt;&lt;a href="https://learn.microsoft.com/en-us/dynamics365/marketing/dynamic-email-content"&gt;Dynamic Content&lt;/a&gt;&lt;/strong&gt;, you might already have an idea of the implementation details. However, I do want to bring up one caveat of dynamic content before discussing the details of my solution.&lt;/p&gt;




&lt;p&gt;According to Microsoft Docs - &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Dynamic content gets resolved just before a message is sent to a specific individual. You'll typically use dynamic content to merge information from the recipient's contact record (such as first and last name), to place special links, and to place information and links from the content settings.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The caveat is that although you can populate dynamic content using information of records related to the recipient's contact record, you cannot traverse more than one relationship down the path from the contact table. For example, you can use a contact's parent account name to seed dynamic content because account table is &lt;strong&gt;directly&lt;/strong&gt; related to contact table in D365 (only one relationship away). On the flip side, it is not feasible to use the parent account's territory information in dynamic content since territory table is only directly related to account table (two relationships away from contact table). Even if you use the dynamic content syntax like "contact.contact_account_parentcustomerid.territoryid.name", it will not resolve to the name of territory in the actual email sent. This is also specified in a Microsoft documentation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jgnPsSGS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/chkgl1vxxybhny2rmp1z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jgnPsSGS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/chkgl1vxxybhny2rmp1z.png" alt="Image description" width="876" height="133"&gt;&lt;/a&gt;&lt;/p&gt;
Since my example has 3 hops (periods) in the expression, it will not resolve according to Microsoft.






&lt;p&gt;What does it imply? You might be tempted to use the out-of-box table &lt;strong&gt;Email Signature&lt;/strong&gt; (if you are not familiar with Email Signature, check out this &lt;a href="https://centricconsulting.com/blog/how-to-create-an-email-signature-in-dynamics-365-customer-engagement/"&gt;amazing article&lt;/a&gt; by Allen Paige) to populate dynamic content for an email but it is not a viable solution since the email signature table is not directly related to the contact table. You could customize the table to add a direct one-to-many relationship to the contact table but this is very wrong, &lt;strong&gt;semantically&lt;/strong&gt;. How is an email signature of a system user directly related to a contact?&lt;/p&gt;

&lt;p&gt;To work around this impedement, we could use the first name and last name fields in combination as dynamic content but this approach falls short if you want to include additional information ,format the email signature or attach written signature pictures.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--axFeOW9C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kqdex47nsxruw1j6w8p5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--axFeOW9C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kqdex47nsxruw1j6w8p5.png" alt="Image description" width="230" height="174"&gt;&lt;/a&gt;&lt;/p&gt;
Signature with formatted text and picture






&lt;p&gt;Are we stranded here? Rich text field to the rescue! When creating a new column to store text in Dataverse tables, we are given the format option of "Rich Text".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GMnHsBaW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7gwba1f68pv33vl82b81.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GMnHsBaW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7gwba1f68pv33vl82b81.png" alt="Image description" width="339" height="541"&gt;&lt;/a&gt;&lt;/p&gt;
Column creation with rich text format






&lt;p&gt;When "Rich text" format is chosen together with data type of "Multiple lines of text", the data stored in this field will be plain HTML and the field can be edited with rich text control when using placed on the system user table form.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NdSjwr-m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o8fuleu6m48l4fs9rxuf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NdSjwr-m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o8fuleu6m48l4fs9rxuf.png" alt="Image description" width="535" height="476"&gt;&lt;/a&gt;&lt;/p&gt;
Rich text editor on a form of model-driven app






&lt;p&gt;When adding images using the rich text control, make sure to use the "&lt;strong&gt;Web Address&lt;/strong&gt;" option instead of "&lt;strong&gt;From File&lt;/strong&gt;".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Le9eBDne--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kejwmgahgw1y1hogdokj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Le9eBDne--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kejwmgahgw1y1hogdokj.png" alt="Image description" width="417" height="355"&gt;&lt;/a&gt;&lt;/p&gt;
Attach images with web address only






&lt;p&gt;&lt;strong&gt;&lt;em&gt;Since image uploaded to D365 will only be available in the D365 environment but not be publically accessible on the internet, resulting in broken images when recipients open the email.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--weLNDz9F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5p8lndnmya43pqx1c1nd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--weLNDz9F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5p8lndnmya43pqx1c1nd.png" alt="Image description" width="443" height="232"&gt;&lt;/a&gt;&lt;/p&gt;
Broken image since image can only be accessed within D365






&lt;p&gt;Once the record is saved, the signature is stored as plain HTML which can be injected into marketing emails through dynamic content using this syntax: "contact.owninguser.hsi_emailsignature". Since emails are built with HTML behind the scene, the signature's HTML will be injected as a part of the whole HTML body, which will be rendered by email engines as user interface.&lt;/p&gt;

&lt;p&gt;Let's perform a test send and see the final result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--psVwkc7x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0w0ql07hajwdjt9r0e9h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--psVwkc7x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0w0ql07hajwdjt9r0e9h.png" alt="Image description" width="584" height="370"&gt;&lt;/a&gt;&lt;/p&gt;
I used a relatively large dimension for image here so the signature might not look appealing






&lt;p&gt;Now you have learnt how to add some advanced personalization to your marketing emails, congratulations!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How NOT to manage your D365 Marketing instances</title>
      <dc:creator>rainforss</dc:creator>
      <pubDate>Sun, 06 Nov 2022 21:16:47 +0000</pubDate>
      <link>https://dev.to/rainforss/how-not-to-manage-your-d365-marketing-instances-4f65</link>
      <guid>https://dev.to/rainforss/how-not-to-manage-your-d365-marketing-instances-4f65</guid>
      <description>&lt;p&gt;Transferring data and configurations between environments might not be as simple as it seems if instances of D365 Marketing are involved.&lt;/p&gt;

&lt;p&gt;Although Microsoft's official documentation for D365 Marketing App states that Configuration Migration tool can "move validated journeys, emails, and other content from a sandbox to a production environment", the "other content" piece may easily fool implementers into thinking that cotent like marketing pages and images can be moved with ease, which is often not the case.&lt;/p&gt;

&lt;p&gt;In this article, I am going to illustrate why creating all pre-approved content in a sandbox environment with D365 Marketing app might introduce a lot of issues for deployment to production.&lt;/p&gt;




&lt;p&gt;Let's start with one of the simplest issues, which just happens to be one of the easier-to-resolve issues. &lt;/p&gt;

&lt;p&gt;If you have a live marketing page in the test instance of D365 Marketing app, it would be hosted on the Powerapps Portal installed with marketing application. Looking at the full page URL of this marketing page, you would notice the development related domain:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XXMHI5hy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6rw4g6wnzpukst652uks.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XXMHI5hy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6rw4g6wnzpukst652uks.png" alt="Full page URL of marketing page in development environment" width="582" height="576"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
Marketing page of development environment





&lt;p&gt;Once this page has been migrated to another environment with D365 Marketing app, the full page URL would still be the same - pointing to the Powerapps Portal of the source environment. This happens because the Configuration Migration tool simply copies all data to the target environment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iDSnj0VO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rljuiu8whwbhjmz8wng3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iDSnj0VO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rljuiu8whwbhjmz8wng3.png" alt="Marketing page migrated to another environment" width="581" height="471"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
Marketing page migrated to another environment





&lt;p&gt;This is an issue because the page URL would clearly indicate that the page is in a development environment and this page would cease to exist once sandbox Powerapps Portal is disposed of. There is a simple resolution to the issue: stop this live marketing page and go live again (go live directly if page is in "Draft" mode), the system will do a self-correction and publish this marketing page under the Powerapps Portal of the new environment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nLBCRcqN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ct206on2w1emx7ui3xc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nLBCRcqN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ct206on2w1emx7ui3xc.png" alt="Page URL after re-publishing" width="571" height="572"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
Page URL after re-publishing





&lt;p&gt;Now you might be wondering - this does not seem like a real issue at all if I can resolve by republishing the marketing page! However, if there are hundreds of landing pages (due to localization and other considerations), this process needs to be automated on all the pages using a Power Automate Flow and there are many other aspects to be considered - for example, system performance impact.&lt;/p&gt;

&lt;p&gt;Generally speaking, this is indeed a relatively small issue considering the minimal disruption to system operation when it is being resolved.&lt;/p&gt;




&lt;p&gt;Onto the one of the complicated issues: image records copied over to the target environment using Configuration Migration Tool are only stored as references to images of the source environment.&lt;/p&gt;

&lt;p&gt;An image uploaded to D365 Marketing using the user interface will be saved to a private directory (for each unique environment) of a Microsoft owned server. Once this process is complete, the image will be cached through a content delivery network and it can be served through the internet with unique URLs. This URL information is then saved with a record of "Image" table in D365.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3962d_zr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1y5rvxw7l6m8tn3hcp90.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3962d_zr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1y5rvxw7l6m8tn3hcp90.png" alt="File upload user interface" width="838" height="699"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
File upload user interface





&lt;p&gt;When image records are migrated to another environment using Configuraiton Migration Tool, the URL information is simply copied over and the image records are merely referencing the URL of images in the source environment. The following picture shows a list of image record, of which the first one is created using upload while the rest are migrated from the source environment:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fqxtPD66--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x01sfwpf7y29ryphizcs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fqxtPD66--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x01sfwpf7y29ryphizcs.png" alt="List of image records" width="880" height="197"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
Note the different identifier of server file directory





&lt;p&gt;Again, this wouldn't be an issue until the source environment (which is usually a development environment) is disposed of. However, if that happened eventually, all marketing emails, forms and pages built using the images will show broken images as the cached images are removed from content delivery networks and server directories as a whole and they are no longer accessible through internet.&lt;/p&gt;

&lt;p&gt;To work around this issue, you need to gather all the images uploaded to the source environment and bulk upload them into the target environment using the out-of-box image upload functionality. Doing so will publish images to a new sub-directory of content-delivery network for the target environment. Unfortunately, all images being used on marketing content such as marketing emails and marketing forms have to be re-embedded to ensure all the marketing contents are referring to the correct image URL. As you can imagine, the effort required to work around the image reference issue grows exponentially with the number of images and the number of contents referencing the images.&lt;/p&gt;




&lt;p&gt;As of today, there isn't a streamlined and automated solution of migration marketing content between D365 Marketing instances but you may not need a migration solution most of the time. &lt;/p&gt;

&lt;p&gt;A D365 Marketing app contains both configuration data and marketing content. Configuration data such as content settings has no difference from other D365 app's configuration data, which can be migrated between environments using Configuraiton Migration tool without any issues. However, marketing content is just like the content managed on any CMS (content-management system). If you are familiar with any CMS like WordPress or SquareSpace, you know that contents are managed in only one environment, but with different statuses - published, draft, etc. The statuses are used to separate test/development content from anything publicaly available and D365 Marketing is using the same design to manage the content lifecycle. &lt;/p&gt;

&lt;p&gt;In my opinion, all content which should be published or likely to be published in the forseeable future can be placed in the production environment. For example, marketing emails and marketing pages can be created before production go-live and kept in the draft status. Some content may be created in a development enviornment to test customizations added to D365 Marketing app and integrations with other applications, but the solution manager should always bear in mind that content in development is disposable.&lt;/p&gt;

&lt;p&gt;Having a solution/environment management and deloyment strategy is key to the success of a project but not everything has to be included in the solution management and deployment pipeline.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to use D365 Marketing form templates the right way</title>
      <dc:creator>rainforss</dc:creator>
      <pubDate>Thu, 18 Aug 2022 06:23:53 +0000</pubDate>
      <link>https://dev.to/rainforss/how-to-use-d365-marketing-form-templates-the-right-way-amg</link>
      <guid>https://dev.to/rainforss/how-to-use-d365-marketing-form-templates-the-right-way-amg</guid>
      <description>&lt;p&gt;&lt;em&gt;The form template is possibly one of the most under-appreciated or under-utilized tools of Dynamics 365 Marketing while its immediate descendant, marketing form, plays a critical role in digital marketing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After some tinkering with form templates, a good portion of D365 Marketing users or even consultants would most likely give up trying to create &lt;strong&gt;&lt;em&gt;scalable&lt;/em&gt;&lt;/strong&gt; templates and seek help from the front-end developers. This is mostly due to the fact that element styles of the a form template would not be applied to new fields added if configured using the form designer.&lt;/p&gt;




&lt;p&gt;Here is a default form template which you would see in any D365 Marketing instance:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--T_QTyW1M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ovzr24dub9jc7ox7raz.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--T_QTyW1M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ovzr24dub9jc7ox7raz.PNG" alt="A default form template" width="880" height="489"&gt;&lt;/a&gt;&lt;/p&gt;
Very basic, but consistent-looking






&lt;p&gt;If any other types of fields are added to the form template (or the form created using the template), this somewhat consistent form template will break.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--raW1Z7eK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pkpx2hmdlokptu20vhrb.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--raW1Z7eK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pkpx2hmdlokptu20vhrb.PNG" alt="Template becomes inconsistent" width="880" height="506"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;The dropdown field, text-area input and the radio buttons cannot be styled using the form designer so people without the necessary HTML/CSS toolset will be stranded. One can try to get some assistance from a developer what if help is not available? Trying to get your dev team's attention to update the border color is probably not very efficient and surely would not be appreciated by the devs.&lt;/p&gt;

&lt;p&gt;Luckily, there is a solution so that you need to ask your developer for favors only once. The solution is documented &lt;a href="https://docs.microsoft.com/en-us/dynamics365/marketing/custom-template-attributes#add-a-meta-tag-to-create-the-setting"&gt;here&lt;/a&gt; in MS Docs about D365 Marketing but I will give a quick run down since certain parts of the document might not be very clear.&lt;/p&gt;




&lt;p&gt;Usually, users would need to apply styling changes to a certain type/group of form elements to keep the form consistent. For example, it makes more sense to change the color of all input labels instead of only one. To achieve this, you would need a custom attribute to manage color of all input labels.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The following part is for people with HTML/CSS knowledge, simply skip this part and pass it to your development resources&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to the HTML editor of the form template and add one meta tag as follows:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"xrm/designer/setting"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"label-color"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"#66b6e3"&lt;/span&gt; &lt;span class="na"&gt;datatype=&lt;/span&gt;&lt;span class="s"&gt;"color"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Label Text Color"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use '-' instead of empty space in the name attribute since this will be referenced in form's CSS. The value attribute can be any hex color and setting "datatype" attribute equal to "color" will enable a color-picker on the form designer UI. Finally, the label attribute is what will be shown on the form designer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next part is slightly trickier. You need to locate the label elements in HTML body and inspect the direct parent elements (or one more level up) so that you can write CSS selectors to apply styling to the target label elements.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z4Ldyu3---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t3zs5gjy814qgobapbsu.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z4Ldyu3---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t3zs5gjy814qgobapbsu.PNG" alt="Inspect the HTML carefully" width="880" height="214"&gt;&lt;/a&gt;&lt;/p&gt;
The label element is wrapped by a div element with "lp-form-field" class and another div element with "marketing-field" class






&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Do not&lt;/strong&gt; use a single generic CSS selector like below. The CSS created here will collide with existing CSS on the page where marketing forms will be hosted:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Do not&lt;/strong&gt; create a custom CSS class and add the class name to all label elements since new field labels added to the form using designer will not have the custom class name attached.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Be specific&lt;/strong&gt; and target all label elements using the pre-existing class names. Note that we have to wrap the hex color attribute value with two comment blocks and the "label-color" text corresponds to the name attribute of the meta tag we created. The hex color code here also needs to be the &lt;strong&gt;same&lt;/strong&gt; as value of the meta tag so they can be synchronized:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.marketing-field&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;.lp-form-field&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;.lp-ellipsis&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c"&gt;/* @label-color */&lt;/span&gt; &lt;span class="m"&gt;#66b6e3&lt;/span&gt; &lt;span class="c"&gt;/* @label-color */&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Once the form template is saved, you will see a customizable field under "Properties" tab of the form designer:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tXl-TtTh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r345d04lvmdo5i5c5e5m.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tXl-TtTh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r345d04lvmdo5i5c5e5m.PNG" alt="Custom attribute enabled" width="880" height="301"&gt;&lt;/a&gt;&lt;/p&gt;
Control of all label color is now enabled






&lt;ul&gt;
&lt;li&gt;Clicking on the colored box beside the input, you can start using the color-picker to modify the color of all labels directly.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;As a demonstration, I have added all the following meta tags (and CSS) to enable multiple general controls designer would need.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"xrm/designer/setting"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"input-row-gap"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"2.5rem"&lt;/span&gt; &lt;span class="na"&gt;datatype=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Input Field Row Gap"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"xrm/designer/setting"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"label-input-gap"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"0.5rem"&lt;/span&gt; &lt;span class="na"&gt;datatype=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Label Input Gap"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"xrm/designer/setting"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"label-color"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"#66b6e3"&lt;/span&gt; &lt;span class="na"&gt;datatype=&lt;/span&gt;&lt;span class="s"&gt;"color"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Label Text Color"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"xrm/designer/setting"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"input-height"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"30px"&lt;/span&gt; &lt;span class="na"&gt;datatype=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Input Height"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"xrm/designer/setting"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"input-border-radius"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"3px"&lt;/span&gt; &lt;span class="na"&gt;datatype=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Input Border Radius"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"xrm/designer/setting"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"input-border-width"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"1px"&lt;/span&gt; &lt;span class="na"&gt;datatype=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Input Border Width"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"xrm/designer/setting"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"input-border-color"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"#ccc"&lt;/span&gt; &lt;span class="na"&gt;datatype=&lt;/span&gt;&lt;span class="s"&gt;"color"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Input Border Color"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"xrm/designer/setting"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"input-focus-color"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"#2b95b8"&lt;/span&gt; &lt;span class="na"&gt;datatype=&lt;/span&gt;&lt;span class="s"&gt;"color"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Input Focused Color"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"xrm/designer/setting"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"button-background"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"#38C6F4"&lt;/span&gt; &lt;span class="na"&gt;datatype=&lt;/span&gt;&lt;span class="s"&gt;"color"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Button Background Color"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"xrm/designer/setting"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"button-text-color"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"#000"&lt;/span&gt; &lt;span class="na"&gt;datatype=&lt;/span&gt;&lt;span class="s"&gt;"color"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Button Text Color"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"xrm/designer/setting"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"button-background-hover"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"#2b95b8"&lt;/span&gt; &lt;span class="na"&gt;datatype=&lt;/span&gt;&lt;span class="s"&gt;"color"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Button Hover Background Color"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"xrm/designer/setting"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"button-text-size"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"1.25rem"&lt;/span&gt; &lt;span class="na"&gt;datatype=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Button Font Size"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"xrm/designer/setting"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"button-position"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"center"&lt;/span&gt; &lt;span class="na"&gt;datatype=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Submit Button Position (start,center,end)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---OthSMEl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f63hq36kqyos6rl1xly4.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---OthSMEl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f63hq36kqyos6rl1xly4.PNG" alt="Enabled custom controls" width="293" height="529"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Now the end users and consultants can apply stylings to groups of elements without always bugging the developers. The users will need the developers' help only when they need additional controls enabled and this will be a rare occation if the available controls are planned out in the beginning. When a new marketing form is created using this template, the custom controls will be immediately available on the form designer and styling changes will be applied to new fields added too.&lt;/p&gt;

&lt;p&gt;This solution greatly improves consistency of marketing form stylings and efficiency of individuals involved so I would definitely recommend trying out.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Manage your Dynamics 365 Marketing Forms properly</title>
      <dc:creator>rainforss</dc:creator>
      <pubDate>Sun, 14 Aug 2022 18:20:48 +0000</pubDate>
      <link>https://dev.to/rainforss/manage-your-dynamics-365-marketing-forms-properly-4igd</link>
      <guid>https://dev.to/rainforss/manage-your-dynamics-365-marketing-forms-properly-4igd</guid>
      <description>&lt;p&gt;&lt;em&gt;Dynamics 365 for Marketing is an advanced marketing automation solution which has seen a great rise in popularity since its unveiling. Because of the complex nature of marketing tools (online marketing, email marketing and real-time marketing) and the fact that D365 Marketing module being "young" in its product lifecycle, this marketing automation solution is deemed to receive more critics than other Dynamics 365 products which are more "mature".&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here is one of the most prominent critiques I have heard working with clients and other D365 Marketing functional consultants:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Customizable can go too far, very involved to develop what is needed. Requires investment in both time and resources. No one seems to know how to get it to work, even the so-called experts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This pain point is very valid since I have seen a many poorly implemented, customized and managed D365 Marketing instances. In this article, I will use Marketing Forms - one of the most used marketing tools for lead capturing - to prove this point and demonstrate how you can properly implement and maintain D365 Marketing Forms for clients to avoid unnecessary frustration.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;&lt;em&gt;The topics discussed in the following section require some knowledge about D365 Marketing Forms&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Generally speaking, marketing forms need to follow a client's branding principles so they can be embedded in the client's public facing website seamlessly. In this case, the marketing forms need to styled accordingly unless your client is willing to use a marketing form which looks like it is coming from a 1980's webpage:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r-BFbTx---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ob57k1jwp87wfioqzf1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r-BFbTx---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ob57k1jwp87wfioqzf1.png" alt="An old HTML web form" width="504" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have used the marketing form designer provided by D365 Marketing, you probably know about - or have already complained about its lack of configurability for styling. Yes you can configure the form's layout, font size, text alignment, text color and paddings for form elements but these functionalities can barely make a web form which is no where near perfect.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OfkLNEm---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v869lao3gmzimb3ef6mj.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OfkLNEm---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v869lao3gmzimb3ef6mj.PNG" alt="OOB form designer" width="684" height="529"&gt;&lt;/a&gt;&lt;/p&gt;
The form editor itself is enough make a basic form somewhat dynamic, but things like perfectly centering the reCAPTCHA block and styling the submit button cannot be done using the user interface






&lt;p&gt;Even if you can let the skewed reCAPTCHA block and unstyled button fly, any new fields being added to the marketing form need to be styled individually using the editor user interface. As a functional consultant, you would probably expect that your time is better spent elsewhere.&lt;/p&gt;

&lt;p&gt;Luckily, the form designer provides an HTML editor which can be used by web developers (or anyone who has enough knowledge about HTML and CSS) to customize web form appearances to whatever the client requires. However, this customization can also bring us to some very messy scenarios which usually lead to the first part of the quoted critique - "Customizable can go too far so the solution requires investments in both time and resources".&lt;/p&gt;




&lt;p&gt;Let's say that you - a functional consultant - has gathered the branding requirements from the customer and added all the fields to the marketing form using the form designer. However, the basic form designer cannot achieve the fancy design of the marketing form so the task is passed to a front-end developer within your development team so the form can be properly styled using custom CSS. This seems like no big deal because every front-end developer (event the juniors) can write CSS and style the form, right?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Unfortunaly, this implementation is very concerning, coming from a solution design perspective.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CSS can be added inline of any HTML element and it can also be added between the style tags using class names. If developers on your development team are not following the exact same enforced coding patterns and class naming principles, this implementation can lead to very a bloaded and messy HTML/CSS code base for all the marketing forms available&lt;/li&gt;
&lt;li&gt;If the marketing form styling needs to be updated following a client's feedback (which happens very frequently) and the task is assigned to another developer, he/she might not be able to follow what the previous dev has done and simply proceeds to add his/her own CSS styling to override the previous styles, which leads to more unnecessary lines of code&lt;/li&gt;
&lt;li&gt;If a new unstyled field is added to the existig form or if there is another similar form which needs some custom styling, you and your developers need to rinse and repeat this painfully inefficient and messy process.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1c747cQY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tocuj23m5zxlnkqxoif8.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1c747cQY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tocuj23m5zxlnkqxoif8.PNG" alt="Field inconsistency" width="773" height="607"&gt;&lt;/a&gt;&lt;/p&gt;
A new text field is dropped onto the form, see the inconsistency of field size?






&lt;p&gt;If we go down this route, we are relying on a group of people's (from all senority levels) diligence just to implement and maintain ONE marketing form. Also, the functional consultants (without solid HTML/CSS experience) will ALWAYS rely on the developers for custom styling changes. Furthermore, If people made the code base a giant pile of spaghetti, it'll be impossible for technical supports to troubleshoot and often customers quote "no one knows what is going on, even the so-called experts",&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;What could be a more elegant solution? Make use of form templates and custom attributes to enable additional form designer options for non-technical D365 Marketing users.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---jmztpf8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6f80ymvy0k5a6ja72jvc.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---jmztpf8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6f80ymvy0k5a6ja72jvc.PNG" alt="Form template with custom attributes" width="880" height="506"&gt;&lt;/a&gt;&lt;/p&gt;
If you look closely to the right, you can notice the all the additional configurations which cannot be done using out-of-box form designer






&lt;p&gt;Sometimes we do criticize Microsoft for poorly documented features but this would not be one of those occasions: the custom attribute feature is documented &lt;a href="https://docs.microsoft.com/en-us/dynamics365/marketing/custom-template-attributes"&gt;here&lt;/a&gt; under MS Docs for Dynamics 365 Marketing.&lt;/p&gt;

&lt;p&gt;To summarize the implementation process, one of your developers would need to add all the commonly used form elements (text fields, dropdown fields, radio input, etc.) to a marketing form template and enable custom configuration attributes such as the gap between each form field and the color of the input labels. This can be done by adding meta tags to the form template's HTML and changing related CSS properties to a templated format. If you are interested about the details of setup, I will release a guide in the future.&lt;/p&gt;

&lt;p&gt;So what are the benefits of this implementation?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The types of form fields are definite and the client would only need certain level of control over the form fields (field gaps, button color, button color when hovering, etc.), all of which can be implemented by a developer at once on a marketing form template. This means that you have less dependencies involved so the client does not continuous support on small things like changing a button's background color.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Admittedly, this implementation does not completely eliminate the risk of poorly coded CSS, but it does alleviate the severity of the issue by having developers implementing the code base only once.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The custom configuration can be generic so that you or the client can simply change, for example, the color or font size of all input labels by modifying only one field. The styling change will also be applied to &lt;strong&gt;new fields&lt;/strong&gt; being added to marketing forms.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The migration process to another environment is simle and streamlined. Simply copy the HTML of the marketing form template and paste it into a new marketing form template created in another environment and you can start using the template for new forms.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;_Let's see it in action. _&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--C2ihicz---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yc69pjxoem6d7vixefkb.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--C2ihicz---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yc69pjxoem6d7vixefkb.PNG" alt="Form configuration using template" width="880" height="424"&gt;&lt;/a&gt;&lt;/p&gt;
When you change the configuration, for example, the label text color using the color-picker, all labels change color.






&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bEwUB3-8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tkpggam1ctm9ggwdc548.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bEwUB3-8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tkpggam1ctm9ggwdc548.PNG" alt="Adding new fields to form" width="880" height="523"&gt;&lt;/a&gt;&lt;/p&gt;
When a new form element is added, it follows the same styling configuration as existing fields.






&lt;p&gt;This implementation makes it easier for anyone (even without knowledge of HTML/CSS) to control advanced styling attributes, removing the need of constant support from developers. Also, if a client complains to you about certain configuration not working, you know that it won't be some "mysteries" hidden in HTML/CSS of the form.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;&lt;em&gt;As always, try not to settle with a solution which "just works". Design a solution that makes everyone's job hassle-free and it is what a marketing automation solution supposed to do.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>dynamics365</category>
      <category>marketing</category>
    </item>
    <item>
      <title>D365 Marketing Email and Website Tracking</title>
      <dc:creator>rainforss</dc:creator>
      <pubDate>Tue, 03 May 2022 21:33:13 +0000</pubDate>
      <link>https://dev.to/rainforss/d365-marketing-email-and-website-tracking-43b7</link>
      <guid>https://dev.to/rainforss/d365-marketing-email-and-website-tracking-43b7</guid>
      <description>&lt;p&gt;Marketing plays a crucial role in the expansion and growth of a business and there has been a rise in popularity of the marketing automation tools/services. Serving marketing content to your target audience through your website or marketing email is only the beginning of the customer journey and implementing a personalized marketing strategy based on audience's reaction is most likely a more important piece of the puzzle. This is why the big wigs - D365 Marketing from Microsoft being one of them - of marketing automation tool/service providers offer both website traffic tracking and email tracking in their product bundle.&lt;/p&gt;

&lt;p&gt;As a marketing specialist, it would be a blessing to know your audience's interactions and engage new conversations accordingly with the help of these tools. However, there are caveats that you might not be aware of if you do not have enough knowledge about how tracking - &lt;strong&gt;especially email tracking&lt;/strong&gt; - is accomplished.&lt;/p&gt;




&lt;p&gt;Website interaction tracking is generally more capable than email interaction tracking since it is realized using Javascript - a script that runs in the browser. Remember the code snippet you were asked to embed in your website to enable website traffic tracking?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataLayer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataLayer&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;gtag&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataLayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;);}&lt;/span&gt;
  &lt;span class="nx"&gt;gtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

  &lt;span class="nx"&gt;gtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GA_MEASUREMENT_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That code snippet was a piece of Javascript code, or sometimes a reference to some Javascript code hosted on the internet. When someone loads up your website in the browser, the code snippet starts running in the browser and the script has methods called "event listeners" that monitors all kinds of interactions. The abundance of event listeners in Javascript language makes it possible that almost any interaction can be captured, for example, user clicks on any section of the web page or user scrolls up/down the page.&lt;/p&gt;

&lt;p&gt;Being very powerful at capturing user interactions, the website tracking has one major limitation - the user is (or should be) anonymous most of the time. Now you might be asking, "What about service offerings like Visitor Queue"? Yes some services like Visitor Queue can provide a bit more details about individual visitors to your website since their Javascript tracking code looks up the IP address of the visitors and tries to match the IP address with a company or organization. In this case, you would know that someone in a "Foo" company might be interested in your marketing content but you still do not have concrete information about the 'someone' who visited your website. Also, this type of tracking &lt;em&gt;may or may not&lt;/em&gt; be legal depending on the territory where your business operates in so &lt;strong&gt;ALWAYS&lt;/strong&gt; make sure that tracking services you are using would not bring you &lt;strong&gt;lawsuits&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In short, website tracking can bring you very detailed information about visitors' interactions but not the individual visitor. Therefore, most of the website tracking services (for example Google Analytics) provide you with analytical data which can guide you to better design and organize your website content and properly funnel your visitors to a CTA button or form submission.&lt;/p&gt;




&lt;p&gt;Email tracking would be the opposite of website tracking. You know the exact person (because you have the person's email hence the contact record) who interacted with your marketing email but you will not be able to know if a user clicks on a paragraph or a heading of your email content.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FwZhS3lz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a4gw4g73kk0x43jj1e50.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FwZhS3lz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a4gw4g73kk0x43jj1e50.PNG" alt="Email click map" width="697" height="707"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looking at the email click map provided by D365 Marketing email insights, you can notice that only clicks on hyperlinks are tracked. If a heading in the email does not have a hyperlink embedded, you would not see the click interactions on the heading at all. On the other hand, this same interaction can easily be captured in website tracking. But why?&lt;/p&gt;

&lt;p&gt;Since emails contain very confidential information to users, email clients &lt;strong&gt;DO NOT ALLOW&lt;/strong&gt; any scripts embedded in the email HTML. Without the help of scripting, email tracking services are not able to capture any interactions without a work-around that being the use of &lt;em&gt;hyperlinks&lt;/em&gt;. To illustrate, imagine if you have embedded a hyperlink &lt;a href="//www.betach.com/about"&gt;About Us&lt;/a&gt; in the marketing email and this hyperlink is actually an HTML element that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"www.betach.com/about"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;About Us&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you save this hyperlink in the email editor, the marketing automation tool will further encrypt the original url (&lt;a href="http://www.betach.com/about"&gt;www.betach.com/about&lt;/a&gt;) into another url that points to a proxy server (most likely the server of the marketing automation service). Have a look at the following screenshot, you can notice that the url actually points to a server 'svc.dynamics.com' with some unique identifiers provided.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OKEUWQY5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oa7e80yi9dgvy2uxxuct.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OKEUWQY5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oa7e80yi9dgvy2uxxuct.PNG" alt="marketing email screenshot" width="608" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When this link is clicked, a browser tab will open to send an HTTP request to this url where the marketing application server intercepts the request, looks up the contact record and actual url using the unique identifiers, increments the user click count by 1 in database and then redirects the page to the actual url: &lt;a href="http://www.betach.com/about"&gt;www.betach.com/about&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;With the above setup, email tracking services can record recipients' click on hyperlinks without any scripts embedded in the email body.&lt;/p&gt;

&lt;p&gt;One of my clients who had some background knowledge in HTML tried to embed an HTML link which contains a sales-rep's email as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"mailto:xxx@xxx.com"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Contact your sales rep&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the marketing email was set up, he wondered why he could not use the 'click of contact your sales rep button' as a criteria to segment the customer journey. With previous explanation, this is now easier to interpret:&lt;/p&gt;

&lt;p&gt;Although this HTML element is also rendered as a hyperlink in the email clients, a click on the link would only open up a new email in the default email client on your desktop with the recipient field pre-populated with the email address. Since this behaviour is &lt;strong&gt;completely irrelevant&lt;/strong&gt; to the browser, no HTTP request is made to the proxy marketing server and the marketing server has no way of recording the click event. Most of the time, the marketing automation's email editor will disregard this type of hyperlink and will not encrypt the link href property at all.&lt;/p&gt;




&lt;p&gt;Another caveat worth mentioning is that 'email open' events tracked by email automation services might not be accurate as you anticipated. The most popular method to track email opens is implemented by a pixel image embedded in the marketing email body. The pixel image itself is hosted, as you might have guessed, in the marketing automation service's own server. When the email is opened in an email client, the email client attempts to send an HTTP request to the marketing server asking for the pixel image when the marketing server intercepts this request and record the event as email being opened. Because of security concerns, major email clients such as Gmail have started enforcing the email to not make HTTP request to remote resources unless the user explicitly allows so by clicking a button to start fetching the resources. With the enforcement in place, a good amount of email opens could have been missed since users could read the email content without downloading the extra image files.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The key takeaway here is that marketing specialists utilizing both tracking tools should have a good understanding of the underlying mechanisms. Without a solid background knowledge of these tools, you could mix up the capabilities of them and go down a pitfall where things are not working as you expect them to and you would have a hard time troubleshooting.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dynamics365</category>
      <category>emailtracking</category>
    </item>
    <item>
      <title>Going serverless with custom portal for D365 environments</title>
      <dc:creator>rainforss</dc:creator>
      <pubDate>Fri, 22 Apr 2022 08:23:21 +0000</pubDate>
      <link>https://dev.to/rainforss/going-serverless-with-custom-portal-for-d365-environments-2e4j</link>
      <guid>https://dev.to/rainforss/going-serverless-with-custom-portal-for-d365-environments-2e4j</guid>
      <description>&lt;p&gt;&lt;em&gt;In the &lt;a href="https://dev.to/rainforss/test-4h3k"&gt;first article&lt;/a&gt; of the series, I "briefly" addressed the pros and cons of Microsoft's Power Apps Portal and the reason why it might not be a cost-efficient product for companies at all scales who need to open a controlled "portal" to a specific piece of their D365 data.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The standalone web server which is supporting the Power Apps Portal plays a major role depicting the product as a non-optimal solution for smaller organizations. Most of the time, a business operating in a single territory (or territories in a close proximity) does not need a web server which continuously listens for incoming requests since the traffic could be very localized and patterned. For example, Calgary Canucks Rugby Union - a non-profit organization operating in Calgary - tries to expose some matches information stored in Dynamics 365 through Power Apps Portal. It is very likely that traffic would hit the portal some time right after the rugby matches and rapidly die down until the next match result is posted. During this quiet period between the matches, utilization rate of the portal web server would be very low so the server will stay in an idle state awaiting requests. Therefore, smaller organizations using Power Apps Portal are essentially paying for a web server that's idling for more than 70% of the time.&lt;/p&gt;

&lt;p&gt;One can propose the use of a &lt;em&gt;lower tier web server&lt;/em&gt; (like what Heroku offers for the free hosting) which enters a sleep mode if no traffic come in for a period of time, but I am going to take it further by adopting a &lt;a href="https://martinfowler.com/articles/serverless.html"&gt;&lt;strong&gt;serverless solution&lt;/strong&gt;&lt;/a&gt;, removing a traditional server from the scene entirely.&lt;/p&gt;

&lt;p&gt;Being a huge fan of &lt;a href="https://nextjs.org/"&gt;&lt;strong&gt;NextJS&lt;/strong&gt;&lt;/a&gt; (an opinionated framework based on ReactJS) and serverless functions, I decided to use NextJS to power the front-end of my custom portal and &lt;a href="https://nextjs.org/docs/api-routes/introduction"&gt;&lt;strong&gt;NextJS API routes&lt;/strong&gt;&lt;/a&gt; (together with Vercel's serverless functions) to orchestrate my portal's back-end services.&lt;/p&gt;

&lt;p&gt;In short, &lt;strong&gt;serverless functions&lt;/strong&gt; are invoked functions running in servers offered by larger organizations (AWS, Azure, Google, Vercel, etc.) whose profession is in cloud computing. Using serverless functions to access other web services (e.g., making HTTP requests to remote servers), the front-end application is enabled to do its job (providing user interactions) without the need of a dedicated traditional server. With the self-owned traditional server out of the solution, smaller organizations can focus on delivering user-centric content instead of sparing resources on maintaining a web server.&lt;/p&gt;

&lt;p&gt;Now you might be asking, how do serverless functions communicate with Dynamics 365 environments to retrieve the data for the front-end web application? Making use of Azure App Registration and a application user in Dynamics 365, the serverless functions can obtain access tokens issued by Azure Active Directory OAuth2.0 service. Adding the access token to HTTP requests made by serverless functions, the web application is now authenticated and authorized as the application user to perform CRUD operations allowed by security role configurations. This is just a &lt;strong&gt;high-level&lt;/strong&gt; overview of the integration, feel free to ask if you want to know more about the details.&lt;/p&gt;

&lt;p&gt;Using the serverless functions, I was able to create a custom D365 portal with even registration and authentication functionalities for Calgary Canucks Rugby: &lt;a href="https://www.canucksrugby.org/"&gt;Calgary Canucks Rugby Union&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How does serverless function save companies' money?
&lt;/h2&gt;

&lt;p&gt;Serverless functions are on-demand services provided by cloud computing companies so you only need to pay for the functions being invoked. In the previously illustrated scenario, Calgary Canucks Rugby simply wouldn't have any expenditure when no traffic is hitting (hence no back-end services involved) the public facing website if serverless function was the back-end solution.&lt;/p&gt;

&lt;p&gt;To make the serverless solution even better, companies such as Vercel are also offering free serverless function usage per month and it is actually very rare for front-end applications with less traffic to use up the monthly quota.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--v9mN9S0o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z0jzb2vtok8dbf9dpacu.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--v9mN9S0o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z0jzb2vtok8dbf9dpacu.PNG" alt="Vercel Pricing" width="880" height="669"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By adopting the serverless function solution for a custom Dynamics 365 portal, Calgary Canucks Rugby and other similar organizations can save up a good amount of budget for other essential operations simply because they wouldn't be paying for web servers and web server maintenance. Sorry "webmasters" and "IT administrators" :(&lt;/p&gt;




&lt;p&gt;&lt;em&gt;In case you are wondering, this article is not sponsored by Vercel or NextJS. I simply love Vercel and NextJS that much :)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>nextjs</category>
      <category>react</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Using Redis Cloud in your NextJS application</title>
      <dc:creator>rainforss</dc:creator>
      <pubDate>Mon, 18 Apr 2022 01:10:02 +0000</pubDate>
      <link>https://dev.to/rainforss/using-redis-cloud-in-your-nextjs-application-39f2</link>
      <guid>https://dev.to/rainforss/using-redis-cloud-in-your-nextjs-application-39f2</guid>
      <description>&lt;p&gt;Recently, a Youtuber and Javascript influencer FireShip released a &lt;a href="https://www.youtube.com/watch?v=DOIWQddRD5M&amp;amp;t=334s&amp;amp;ab_channel=Fireship" rel="noopener noreferrer"&gt;tutorial&lt;/a&gt; of utilizing Redis Enterprise Cloud as a high speed cloud data storage in a NextJS web application and the tutorial attracted hundreds of thousand of views on the internet. Although I was on the fence about using an in-memory database as the main database for a full-stack application, the Redis Enterprise Cloud service offer - together with a $200 voucher - had me interested enough to try it as a runtime cache of a NextJS application I built for a product demo to York University.&lt;/p&gt;

&lt;p&gt;Arriving at Redis Enterprice Cloud service offering page, I picked the free tier since I was not certain if I would start spending the $200 credit yet. The free service tier - as you would imagine for anything that is free - does not offer a lot: 30MB RAM for 1 dedicated database with &lt;strong&gt;30 maximum concurrent connections&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fadqf02q6yhezk66m26l9.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fadqf02q6yhezk66m26l9.PNG" title="Redis Enterprise Cloud pricing information" alt="Redis Enterprise Cloud pricing"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;30 maximum connections may not seem like an issue as long as you are not building an application that has specific requirements for concurrency. This statement could be true if we are establishing connections between a Node server and a Redis cache since it is &lt;a href="https://github.com/redis/node-redis/issues/558" rel="noopener noreferrer"&gt;recommended that only one or two Redis client would be instantiated then reused&lt;/a&gt; in the Node server. In this case, there is a limited number of connections (&lt;a href="https://stackoverflow.com/questions/51517578/how-many-total-connection-or-max-connections-are-available-in-redis-server" rel="noopener noreferrer"&gt;clients are connections in Redis&lt;/a&gt;) needed when the server is running and communicating with Redis.&lt;/p&gt;




&lt;p&gt;However, things are very different if you are using Redis Enterprise Cloud in a NextJS application and having your backend services orchestrated using the &lt;strong&gt;Serverless Functions&lt;/strong&gt;. If you do not understand the basics of NextJS and Serverless Functions enough, you would encounter this error and never understand the cause of it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ERR max number of clients reached&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If I am only connecting to Redis with one application, how is it possible that all 30 connections are occupied? Let's look at some code before I reveal the answer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Repository&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redis-om&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIS_URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a code piece copied from &lt;a href="https://fireship.io/lessons/redis-nextjs/" rel="noopener noreferrer"&gt;Fireship's tutorial&lt;/a&gt;. It instantiates a Redis client with the help of &lt;a href="https://www.npmjs.com/package/redis-om" rel="noopener noreferrer"&gt;"Redis-OM"&lt;/a&gt; library and declares a "connect" function which can be used to establish a connection between the client and Redis. As the code specifies, the "connect" function will only attempt to open a connection if no open connections exist between the client and Redis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIS_URL&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;connect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Already connected to Redis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Connected successfully.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is another code piece if the &lt;a href="https://www.npmjs.com/package/redis" rel="noopener noreferrer"&gt;"Node-Redis"&lt;/a&gt; library is being used. There are slight differences but the logic is the same: open a connection only if there is no existing connection established between the client and Redis.&lt;/p&gt;

&lt;p&gt;Either piece of code makes sense on paper here: since the code only gets executed once when the application is up and running, only one instance of client is created and the "connect" function will create the connection only once no matter how many times it is called, right?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrong&lt;/strong&gt;. Remember that NextJS's &lt;em&gt;Serverless Functions&lt;/em&gt; and &lt;em&gt;Edge Functions&lt;/em&gt; are executed in a &lt;strong&gt;highly dynamic environment&lt;/strong&gt; based on resource allocation, meaning the environment where the functions are executed will not be the same as the environment where your NextJS was built and deployed in (e.g. Vercel, Netlify, etc.). Therefore, when you are creating NextJS API routes (these routes are realized by serverless functions if the NextJS app is deployed in Vercel, Netlify or similar platforms) under the /pages/api folder to have them call the "connect" function and then interact with data in Redis, it is almost inevitable that every serverless function execution will be in a &lt;strong&gt;different environment&lt;/strong&gt; where the connection to Redis has not been opened before. With this setup, each serverless function invocation because of API calls will attempt to create a unique connection to your Redis Cloud database. Theoretically, 30 invocations of serverless functions which try to connect to Redis could exhaust the allowed number of connections for free tier. To make things worse, Redis does not have a default setting for connection timeout so the connections created will be kept alive unless your client-side code does the clean up.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you are already stranded with Redis Cloud having maximum number of connections, connect to Redis Cloud using RedisInsights and run the &lt;code&gt;CLIENT KILL TYPE normal&lt;/code&gt; command to evict all the basic connections.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;In a serverless NextJS application setup, it is imperative to always &lt;strong&gt;close the connection&lt;/strong&gt; after your code finishes interacting with Redis data. To complete my tutorial, here is an example setup I am using to handle Redis connections and interactions in a NextJS app supported by serverless functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Initialize the Redis client in global execution context to ensure uniqueness of the client&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;//If no Redis client is found, create the client using Redis connection string&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIS_URL&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;//Open connection only when there is no existing connection&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;connect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Already connected to Redis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Connected successfully.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;//Close connection only when there is an existing connection&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;disconnect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Disconnected.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;//Retrieve the cached data&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getcache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cacheBetachSite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The comments are pretty self-explanatory so I will not go into too much details here. If you are wondering how these helper functions are being used in API routes or NextJS SSG/SSR functions, here are two examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NextJS SSG/SSR functions
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getStaticProps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GetStaticProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cachedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getcache&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;cachedData&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;ul&gt;
&lt;li&gt;API routes supported by serverless functions
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;demoRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;_req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cachedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getcache&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cachedData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unknown error.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Being a part of the IT community, we have the blessing that one can always find tutorials or documentations about a variety of technologies and tools, however, tutorials or documentations might not always fit into each individual's own use case seamlessly. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Be &lt;strong&gt;adventurous&lt;/strong&gt; and &lt;strong&gt;diligent&lt;/strong&gt;, always do your own recon before following others' footprints.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>javascript</category>
      <category>nextjs</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Is the Power Apps Portal a viable option as your customer facing website?</title>
      <dc:creator>rainforss</dc:creator>
      <pubDate>Sun, 17 Apr 2022 04:40:03 +0000</pubDate>
      <link>https://dev.to/rainforss/is-the-power-apps-portal-a-viable-option-as-your-customer-facing-website-5316</link>
      <guid>https://dev.to/rainforss/is-the-power-apps-portal-a-viable-option-as-your-customer-facing-website-5316</guid>
      <description>&lt;p&gt;Microsoft has always been promoting the Power Apps Portal (previously Dynamics Portal) as a "no-code" solution for external facing websites.&lt;/p&gt;

&lt;p&gt;Together with the naming change, the pricing model of this Microsoft product has also seen significant changes.&lt;/p&gt;

&lt;p&gt;Prior to &lt;em&gt;1st October 2020&lt;/em&gt;, a customer had to purchase a portal with a flat one-time payment and that pricing model was deemed less profitable since the major clients' portals had very high traffic volume of unauthenticated and authenticated visitors. From 1st October 2020, Microsoft has decided to adopt a &lt;strong&gt;consumption-based&lt;/strong&gt; pricing scheme which makes customers pay monthly fees based on the number of page views and login sessions.&lt;/p&gt;

&lt;p&gt;The following screenshot demonstrates the updated consumption-based pricing model:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XnKHcn_T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tx68gjuqlh4b7a0mwtat.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XnKHcn_T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tx68gjuqlh4b7a0mwtat.png" alt="Power Apps Portal pricing model" title="Price chart of Power Apps Portal" width="700" height="614"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you found this pricing model confusing (which I did), this &lt;a href="https://thehosk.medium.com/understanding-power-portals-pricing-and-how-it-differs-from-dynamics-portals-c1753710e34b"&gt;amazing article&lt;/a&gt; by Ben Hosking gives crystal clear descriptions about the updated pricing model, together with some beneficial background knowledge and thoughts about the portals.&lt;/p&gt;

&lt;p&gt;Although the pay-as-you-go service model may seem cost-effective for consumers who have lower traffic volume and less registered portal users, bigger customers might not enjoy this change since they were always paying the same amount &lt;strong&gt;regardless of the amount of traffic&lt;/strong&gt; hitting the portal. To make the situation even worse, there are some caveats that portal owners are required to pay at the &lt;strong&gt;beginning of the month&lt;/strong&gt; and the purchased consumption amount (pageviews and logins) &lt;strong&gt;DOES NOT&lt;/strong&gt; roll over to the next month if not used up.&lt;/p&gt;

&lt;p&gt;Let's consider the following scenario to understand the grievance your customers might potentially have: A university is using a Power Apps Portal to manage student/staff/alumni logins and the IT department is managing the portal monthly payment based on the averaged number of logins per month. The employment center of the university is holding a career fair in the coming month which will result in a huge influx of authenticated users. However, not knowing the pricing model of Power App Portals, the employment center does not notify the IT department of the event and the greatly increased amount of logins would cause service disruptions of the portal. Furthermore, assuming the employment center learnt a lesson and notified IT department to increase the purchased amount of logins, there could be instances where the actual logins are way under the anticipated amount. With the purchased quota not being able to roll over, this would potentially result in thousands of purchased logins in vain.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;key takeaway&lt;/strong&gt; of the previous example is that a customer can experience difficulties determine the proper amount of logins/pageviews and that can lead to either unforseeable service disruptions or wasted budgets. While this issue might not be as severe for bigger organizations since they could work with statistical data to make a decision on a payment plan and there is more expenditure budgetd, medium to small sized customers would find it hard to manage the &lt;strong&gt;traffic fluctuation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There are many other scenarios we can examine to understand the concerns of choosing Power Apps Portal as your solution but I would leave that to future posts. I will quickly summarize my considerations and thoughts of features offered by Power Apps Portal in this article as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Low-code or no-code solution to configure a website&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This could either be a curse or blessing based on different situations. With the low-code or no-code solution, one can quickly start a "okay" public facing website using templates and basic configurations without much technical aptitude. Here is a basic demo portal landing page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1pRq0r9J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/69v4nzs702wgit6lqxvb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1pRq0r9J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/69v4nzs702wgit6lqxvb.png" alt="A basic portal configured with low-code solution" title="Power Apps Portal created with templates and basic configurations" width="880" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;However&lt;/strong&gt;, if more interactions are desired to make the website "lively" (see a "lively" &lt;a href="https://personal-site-mocha-pi.vercel.app/"&gt;custom coded website&lt;/a&gt; and a &lt;a href="https://north52.microsoftcrmportals.com/"&gt;Power Apps Portal&lt;/a&gt; configured by North52 professionals), the use of HTML/CSS/Javascript coding is inevitable. Now you are facing a &lt;a href="https://dictionary.cambridge.org/dictionary/english/conundrum"&gt;condumdrum&lt;/a&gt; of getting a developer to add custom code to a low-code/no-code platform which he/she likely has very little idea about (unless you can find a technical person who is proficient at both coding and Dynamics 365).&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Microsoft takes care of the scaling aspect of Power Apps Portal&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Scalability is a bitter-sweet problem to have (meaning that you are getting more traffic on your web application) and most of the organizations do not have the luxury or opportunity to ever have to worry about this problem. Moreover, the majority of public facing content is usually static (meaning it is likely to be stale for a long period of time) and this type of content can be cached to &lt;a href="https://www.cloudflare.com/en-ca/learning/cdn/what-is-a-cdn/"&gt;content-delivery networks&lt;/a&gt; (CDN), rendering the scalability unnecessary since the content is not served directly by web server when requested.&lt;/p&gt;

&lt;p&gt;Of course there are way more aspects to look into but to summarize, the auto-scaling feature can certainly benefit large organizations while other organizations probably wouldn't make use of the feature at all.&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Power Apps Portal can have a sophisticated security hierachy to manage different level of access.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Again, having a nested security configuration can bring in benefits or hassles based on the complexity of content you have on the external facing website. For the customers that I have previously worked with, only the large scale organizations are utilizing a complicated security role configuration while the relatively smaller ones only have two security levels - authenticated and unauthenticated.&lt;/p&gt;

&lt;p&gt;Therefore, it is likely that you will be paying for features that are rarely used if you are a small to medium sized customer making use of Power Apps Portal.&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Power Apps Portal is using a pay-as-you-go pricing model.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Based on the pricing model, the monthly cost to larger companies who have high traffic volume to the portals could be high and the cost will add up over time. However, these companies have higher budget than others so they can enjor all the benefits offered by Power Apps Portal while not being too concerned about the cost.&lt;/p&gt;

&lt;p&gt;From the perspective of companies which do not have a good amount of visitors on external facing website, the monthly cost could look promising if we are aiming for under 100 logins and 100,000 pageviews per month. Does it mean that Power Apps Portal is a better choice than having some developers code a web application with integration to Dynamics 365? The answer is not definite.&lt;/p&gt;

&lt;p&gt;You might be asking, "what's the catch?" Firstly, you definitely need to spend thousands if you are trying to have developers develop your website but the website's monthly cost will be much lower comparing to Power Apps Portal's. This means that a custom built website could still be the winner if we are talking about a long run. Secondly, moving away from Power Apps Portal once you are onboard will be a gruesome task because you have no access to the source code of Power Apps Portal.&lt;/p&gt;




&lt;p&gt;There are way more features to be discussed but I will stop here for the sake of reader's sanity. I will potentially address more considerations about other features in the future.&lt;/p&gt;

&lt;p&gt;In conclusion, I am not depicting a bad image of Power Apps Portal though it might seem that I am questioning certain aspects of Power Apps Portal. It is actually a solid product offering in the market when compared to other competitors such as Salesforce Portals. Nonetheless, this type of product offering might or might not be the optimal solution for everyone who is seeking an established public facing web application. Make your decision after careful considerations.&lt;/p&gt;

&lt;p&gt;I sincerely hope that you would find this article inspiring or intuitive (at least part of it). I am happy to answer any questions in the comment or have some more discussions :)&lt;/p&gt;




&lt;p&gt;&lt;em&gt;So what would be a better solution if a web application which can securely surface data from Dynamics 365 environment (DataVerse) is desired? I will have this covered in another post, stay tuned.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  The solution stack consists of NextJS, Vercel Serverless Functions, Redis Cloud and Dynamics 365 Web APIs.
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hEQP8lR6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/87jvaim7w26g1ro3xr3r.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hEQP8lR6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/87jvaim7w26g1ro3xr3r.jpg" alt="Custom portal with authentication and integration to Dynamics 365" title="Custom portal for higher education" width="880" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dynamics365</category>
    </item>
    <item>
      <title>Is the Power Apps Portal a viable option as your customer facing website?</title>
      <dc:creator>rainforss</dc:creator>
      <pubDate>Sat, 16 Apr 2022 20:20:42 +0000</pubDate>
      <link>https://dev.to/rainforss/test-4h3k</link>
      <guid>https://dev.to/rainforss/test-4h3k</guid>
      <description>&lt;p&gt;Microsoft has always been promoting the Power Apps Portal (previously Dynamics Portal) as a "no-code" solution for external facing websites.&lt;/p&gt;

&lt;p&gt;Together with the naming change, the pricing model of this Microsoft product has also seen significant changes.&lt;/p&gt;

&lt;p&gt;Prior to &lt;em&gt;1st October 2020&lt;/em&gt;, a customer had to purchase a portal with a flat one-time payment and that pricing model was deemed less profitable since the major clients' portals had very high traffic volume of unauthenticated and authenticated visitors. From 1st October 2020, Microsoft has decided to adopt a &lt;strong&gt;consumption-based&lt;/strong&gt; pricing scheme which makes customers pay monthly fees based on the number of page views and login sessions.&lt;/p&gt;

&lt;p&gt;The following screenshot demonstrates the updated consumption-based pricing model:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XnKHcn_T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tx68gjuqlh4b7a0mwtat.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XnKHcn_T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tx68gjuqlh4b7a0mwtat.png" alt="Power Apps Portal pricing model" title="Price chart of Power Apps Portal" width="700" height="614"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you found this pricing model confusing (which I did), this &lt;a href="https://thehosk.medium.com/understanding-power-portals-pricing-and-how-it-differs-from-dynamics-portals-c1753710e34b"&gt;amazing article&lt;/a&gt; by Ben Hosking gives crystal clear descriptions about the updated pricing model, together with some beneficial background knowledge and thoughts about the portals.&lt;/p&gt;

&lt;p&gt;Although the pay-as-you-go service model may seem cost-effective for consumers who have lower traffic volume and less registered portal users, bigger customers might not enjoy this change since they were always paying the same amount &lt;strong&gt;regardless of the amount of traffic&lt;/strong&gt; hitting the portal. To make the situation even worse, there are some caveats that portal owners are required to pay at the &lt;strong&gt;beginning of the month&lt;/strong&gt; and the purchased consumption amount (pageviews and logins) &lt;strong&gt;DOES NOT&lt;/strong&gt; roll over to the next month if not used up.&lt;/p&gt;

&lt;p&gt;Let's consider the following scenario to understand the grievance your customers might potentially have: A university is using a Power Apps Portal to manage student/staff/alumni logins and the IT department is managing the portal monthly payment based on the averaged number of logins per month. The employment center of the university is holding a career fair in the coming month which will result in a huge influx of authenticated users. However, not knowing the pricing model of Power App Portals, the employment center does not notify the IT department of the event and the greatly increased amount of logins would cause service disruptions of the portal. Furthermore, assuming the employment center learnt a lesson and notified IT department to increase the purchased amount of logins, there could be instances where the actual logins are way under the anticipated amount. With the purchased quota not being able to roll over, this would potentially result in thousands of purchased logins in vain.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;key takeaway&lt;/strong&gt; of the previous example is that a customer can experience difficulties determine the proper amount of logins/pageviews and that can lead to either unforseeable service disruptions or wasted budgets. While this issue might not be as severe for bigger organizations since they could work with statistical data to make a decision on a payment plan and there is more expenditure budgetd, medium to small sized customers would find it hard to manage the &lt;strong&gt;traffic fluctuation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There are many other scenarios we can examine to understand the concerns of choosing Power Apps Portal as your solution but I would leave that to future posts. I will quickly summarize my considerations and thoughts of features offered by Power Apps Portal in this article as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Low-code or no-code solution to configure a website&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This could either be a curse or blessing based on different situations. With the low-code or no-code solution, one can quickly start a "okay" public facing website using templates and basic configurations without much technical aptitude. Here is a basic demo portal landing page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1pRq0r9J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/69v4nzs702wgit6lqxvb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1pRq0r9J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/69v4nzs702wgit6lqxvb.png" alt="A basic portal configured with low-code solution" title="Power Apps Portal created with templates and basic configurations" width="880" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;However&lt;/strong&gt;, if more interactions are desired to make the website "lively" (see a "lively" &lt;a href="https://personal-site-mocha-pi.vercel.app/"&gt;custom coded website&lt;/a&gt; and a &lt;a href="https://north52.microsoftcrmportals.com/"&gt;Power Apps Portal&lt;/a&gt; configured by North52 professionals), the use of HTML/CSS/Javascript coding is inevitable. Now you are facing a &lt;a href="https://dictionary.cambridge.org/dictionary/english/conundrum"&gt;condumdrum&lt;/a&gt; of getting a developer to add custom code to a low-code/no-code platform which he/she likely has very little idea about (unless you can find a technical person who is proficient at both coding and Dynamics 365).&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Microsoft takes care of the scaling aspect of Power Apps Portal&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Scalability is a bitter-sweet problem to have (meaning that you are getting more traffic on your web application) and most of the organizations do not have the luxury or opportunity to ever have to worry about this problem. Moreover, the majority of public facing content is usually static (meaning it is likely to be stale for a long period of time) and this type of content can be cached to &lt;a href="https://www.cloudflare.com/en-ca/learning/cdn/what-is-a-cdn/"&gt;content-delivery networks&lt;/a&gt; (CDN), rendering the scalability unnecessary since the content is not served directly by web server when requested.&lt;/p&gt;

&lt;p&gt;Of course there are way more aspects to look into but to summarize, the auto-scaling feature can certainly benefit large organizations while other organizations probably wouldn't make use of the feature at all.&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Power Apps Portal can have a sophisticated security hierachy to manage different level of access.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Again, having a nested security configuration can bring in benefits or hassles based on the complexity of content you have on the external facing website. For the customers that I have previously worked with, only the large scale organizations are utilizing a complicated security role configuration while the relatively smaller ones only have two security levels - authenticated and unauthenticated.&lt;/p&gt;

&lt;p&gt;Therefore, it is likely that you will be paying for features that are rarely used if you are a small to medium sized customer making use of Power Apps Portal.&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Power Apps Portal is using a pay-as-you-go pricing model.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Based on the pricing model, the monthly cost to larger companies who have high traffic volume to the portals could be high and the cost will add up over time. However, these companies have higher budget than others so they can enjor all the benefits offered by Power Apps Portal while not being too concerned about the cost.&lt;/p&gt;

&lt;p&gt;From the perspective of companies which do not have a good amount of visitors on external facing website, the monthly cost could look promising if we are aiming for under 100 logins and 100,000 pageviews per month. Does it mean that Power Apps Portal is a better choice than having some developers code a web application with integration to Dynamics 365? The answer is not definite.&lt;/p&gt;

&lt;p&gt;You might be asking, "what's the catch?" Firstly, you definitely need to spend thousands if you are trying to have developers develop your website but the website's monthly cost will be much lower comparing to Power Apps Portal's. This means that a custom built website could still be the winner if we are talking about a long run. Secondly, moving away from Power Apps Portal once you are onboard will be a gruesome task because you have no access to the source code of Power Apps Portal.&lt;/p&gt;




&lt;p&gt;There are way more features to be discussed but I will stop here for the sake of reader's sanity. I will potentially address more considerations about other features in the future.&lt;/p&gt;

&lt;p&gt;In conclusion, I am not depicting a bad image of Power Apps Portal though it might seem that I am questioning certain aspects of Power Apps Portal. It is actually a solid product offering in the market when compared to other competitors such as Salesforce Portals. Nonetheless, this type of product offering might or might not be the optimal solution for everyone who is seeking an established public facing web application. Make your decision after careful considerations.&lt;/p&gt;

&lt;p&gt;I sincerely hope that you would find this article inspiring or intuitive (at least part of it). I am happy to answer any questions in the comment or have some more discussions :)&lt;/p&gt;




&lt;p&gt;&lt;em&gt;So what would be a better solution if a web application which can securely surface data from Dynamics 365 environment (DataVerse) is desired? I will have this covered in another post, stay tuned.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  The solution stack consists of NextJS, Vercel Serverless Functions, Redis Cloud and Dynamics 365 Web APIs.
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hEQP8lR6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/87jvaim7w26g1ro3xr3r.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hEQP8lR6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/87jvaim7w26g1ro3xr3r.jpg" alt="Custom portal with authentication and integration to Dynamics 365" title="Custom portal for higher education" width="880" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dynamics365</category>
      <category>portal</category>
      <category>powerapps</category>
    </item>
  </channel>
</rss>
