<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://www.pedromonjo.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.pedromonjo.com/" rel="alternate" type="text/html" /><updated>2026-09-27T18:29:55-04:00</updated><id>https://www.pedromonjo.com/feed.xml</id><title type="html">The Digital Marketing Architect</title><subtitle>Sharing my experience as an Adobe consultant</subtitle><author><name>Pedro Monjo</name></author><entry><title type="html">Dataset Deletion</title><link href="https://www.pedromonjo.com/2026/09/dataset-deletion.html" rel="alternate" type="text/html" title="Dataset Deletion" /><published>2026-09-27T00:00:00-04:00</published><updated>2026-09-27T00:00:00-04:00</updated><id>https://www.pedromonjo.com/2026/09/dataset-deletion</id><content type="html" xml:base="https://www.pedromonjo.com/2026/09/dataset-deletion.html"><![CDATA[<p>The last option you have to delete data in the Adobe Experience Platform (AEP) is to delete the data container: the dataset. I know, this sounds very obvious, but there are some wrinkles that you need to be aware of.</p>

<!--more-->

<p>If you are not familiar with them, I already have a full post on <a href="https://www.pedromonjo.com/2022/08/datasets.html">datasets</a>, which I suggest you review before proceeding with this one. In summary, a dataset is similar to a database table. This is where all the data resides: profile attributes and events. The structure of a dataset is defined by an <a href="https://www.pedromonjo.com/2022/08/xdm-schema.html">XDM Schema</a>.</p>

<h2 id="before-you-delete-a-dataset">Before You Delete a Dataset</h2>

<p>Before you go ahead and delete a dataset, you need to prepare the operation.</p>

<h3 id="system-datasets">System Datasets</h3>

<p>I am sure AEP already protects you from deleting a system dataset, although I have never tried to delete one. In general, the guidance is that you should never delete a system dataset. If you must delete one of them, open a ticket with Adobe Client Care.</p>

<h3 id="data-sources">Data Sources</h3>

<p>For obvious reasons, you should stop sending data to a dataset you want to delete. Check your data sources and disconnect any that are currently populating the dataset. I am not sure whether you can delete a dataset that is connected to an active data source, but I am sure there will be issues if you do so and AEP allows it.</p>

<h3 id="data-usage">Data Usage</h3>

<p>If you have the data in AEP, it is very likely that various AEP components and applications are relying on it. You need to put your Sherlock Holmes hat on and track all the uses of the data that is soon going to be missing:</p>

<ul>
  <li><em>Data Distiller</em>. Check your jobs and stop those that touch the dataset, either as a source or a destination.</li>
  <li><em>Segmentation</em>. Change or delete any segments that rely on the soon-to-be deleted attributes.</li>
  <li><em>Customer Journey Analytics (CJA)</em>. If your dataset is being used in any connection, you need to remove it from the connection.</li>
  <li><em>Adobe Journey Optimizer (AJO)</em>. Perform a thorough inspection of campaigns, journeys and messages, tracking down any reference to the data to be deleted. Update them accordingly.</li>
  <li><em>Real-Time Customer Data Platform (RT-CDP)</em>. Make sure your destinations do not need the attributes that will no longer be there.</li>
</ul>

<p>It is worth mentioning that deleting a dataset does not delete the underlying XDM Schema. In fact, it is not possible to delete an XDM Schema that has been enabled for profile. The consequence is that all data attributes defined in the schema will always be available in the AEP UI. For example, you can create a segment based on a value of an attribute that has no data, the UI will not prevent you; however, no profile will match the condition or the condition will be useless.</p>

<h3 id="people">People</h3>

<p>As important as the technology is, people are even more important. This is probably the most difficult part of the process. You can run some scripts targeting the API to check everywhere the dataset or its data is used. However, finding all the internal users who are relying on the data that is going to be deleted is much more difficult.</p>

<p>Once you find them, you need to notify them that you are planning to delete the dataset and explain to them the consequences:</p>

<ul>
  <li>Output of Data Distiller jobs will not have some or all the data.</li>
  <li>Segments based on that data will not be available or will behave differently.</li>
  <li>Some CJA reports will be missing data.</li>
  <li>Some AJO campaigns and journeys will behave slightly differently or will have to be stopped completely.</li>
  <li>Some AJO messages will have to be updated, as the attributes they were inserting into the body will no longer be populated.</li>
  <li>Some RT-CDP destinations will receive a slightly different audience, or will not receive any new audiences at all.</li>
</ul>

<p>I am sure you will have to work with them on alternatives to keep the business running. Expect friction and pushback.</p>

<h2 id="behind-the-scenes">Behind the Scenes</h2>

<p>You can delete a dataset both through the <a href="https://experienceleague.adobe.com/en/docs/platform-learn/tutorials/data-hygiene/delete-datasets-and-batches">UI</a> and the <a href="https://experienceleague.adobe.com/en/docs/experience-platform/catalog/api/delete-object">API</a>. This is the easy part of this whole process.</p>

<p>Once you submit the request, it will be sent to a queue for processing. Deleting a large dataset will take hours, maybe days, depending on the size. Finally, once the request is processed:</p>

<ul>
  <li>All <a href="https://www.pedromonjo.com/2023/05/real-time-customer-profile.html">profile fragments</a> coming from the dataset will be completely gone. This also means that older fragments coming from another dataset will now be part of the profiles.</li>
  <li>Identities that came from the dataset will be deleted from the <a href="https://www.pedromonjo.com/2025/11/ups-uis.html#unified-identity-service">Unified Identity Service</a>.</li>
  <li>All links between identities provided by the dataset will be removed from the identity graph.</li>
  <li>The dataset will no longer be available in the <a href="https://www.pedromonjo.com/2023/05/data-lake.html">Data Lake</a>.</li>
</ul>

<p>As you can see, deleting a dataset not only deletes the stored data, but it also impacts other systems.</p>

<h2 id="batches">Batches</h2>

<p>I wanted to finalize by briefly mentioning <a href="https://experienceleague.adobe.com/en/docs/experience-platform/catalog/datasets/overview#ingesting-data-into-datasets">batches</a>. You may be tempted to delete only a batch to achieve the same result as deleting a dataset, but limited to the data that was part of the batch. That is, of course, if you know what data is in each batch.</p>

<p>However, this is not how AEP works. If you delete a batch:</p>

<ul>
  <li>The batch records are removed from the Data Lake.</li>
  <li>The processed data remains in the Unified Profile Store.</li>
  <li>Identity graph data is not removed by the batch deletion.</li>
</ul>

<p> </p>

<p><small><a href="https://www.pexels.com/photo/numbers-on-monitor-534216/">Photo by Pixabay</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="Platform" /><summary type="html"><![CDATA[The last option you have to delete data in the Adobe Experience Platform (AEP) is to delete the data container: the dataset. I know, this sounds very obvious, but there are some wrinkles that you need to be aware of.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-09-27-dataset-deletion/data-in-table.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-09-27-dataset-deletion/data-in-table.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Deletion Using TTL</title><link href="https://www.pedromonjo.com/2026/09/deletion-using-ttl.html" rel="alternate" type="text/html" title="Deletion Using TTL" /><published>2026-09-13T00:00:00-04:00</published><updated>2026-09-13T00:00:00-04:00</updated><id>https://www.pedromonjo.com/2026/09/deletion-using-ttl</id><content type="html" xml:base="https://www.pedromonjo.com/2026/09/deletion-using-ttl.html"><![CDATA[<p>In my post on <a href="https://www.pedromonjo.com/2026/08/individual-profile-deletion.html">Individual Profile Deletion</a> I explained a surgical approach to deleting profiles. This approach requires you to actively request the deletion of the data. With the Time-to-Live (TTL) configuration parameters, you can let the Adobe Experience Platform (AEP) take care of the deletion for you in certain scenarios. You set it and forget it.</p>

<!--more-->

<h2 id="edge">Edge</h2>

<p>The <a href="https://experienceleaguecommunities.adobe.com/adobe-experience-platform-18/introduction-to-the-aep-edge-network-14017">AEP Edge Network</a> has its particularities when it comes to managing its data lifecycle. The way to understand it is very simple. The Edge Network receives hundreds or thousands of requests per second and needs to respond in a few hundred milliseconds. If it had a copy of all the data, either it would take seconds, or we would need thousands of servers to manage a single customer.</p>

<p>The solution to the previous issue is obvious: limit the amount of data an Edge server keeps. This limitation comes in two flavors:</p>

<ul>
  <li><a href="https://experienceleague.adobe.com/en/docs/experience-platform/profile/edge-profiles">Edge profiles</a> are kept for up to 14 days after the last time that profile was active.</li>
  <li>Edge events are deleted after 24 hours, which I am sure you already know if you understand the <a href="https://www.pedromonjo.com/2023/08/segment-evaluation-methods-aep.html#edge-segmentation">edge segment</a> qualification rules.</li>
</ul>

<h2 id="events-in-rtcp">Events in RTCP</h2>

<p>When we talk about the concept of “profile” in the <a href="https://www.pedromonjo.com/2023/05/real-time-customer-profile.html">Real-Time Customer Profile</a> (RTCP), we tend to just think of profile attributes. However, AEP profiles are actually comprised of both profile attributes and events.</p>

<p>This has three major consequences:</p>

<ul>
  <li>Profiles grow in size with events.</li>
  <li>Batch segmentation will take longer the more events it has to evaluate.</li>
  <li>Some profiles may only be composed of events, but they still count towards the number of profiles in the license.</li>
</ul>

<p>To avoid profiles from becoming too big, AEP offers a simple solution: <a href="https://experienceleague.adobe.com/en/docs/experience-platform/catalog/datasets/user-guide#data-retention-policy">set a TTL for event datasets in RTCP</a>. This is the span of time an event will be available in the profile store for segmentation.</p>

<p>One typical pushback that we get from customers when we talk to them about setting this TTL is that they want all events in the profile. In this scenario, I start by questioning the need: Marketing campaigns have a greater impact the closer to recent customer activities; as time goes by, the response is going to decrease. If there is a real need for old information, I then recommend converting major events, like transactions, into profile attributes. This can be done using <a href="https://experienceleague.adobe.com/en/docs/experience-platform/query/data-distiller/overview">Data Distiller</a> or <a href="https://experienceleague.adobe.com/en/docs/experience-platform/profile/computed-attributes/overview">Computed Attributes</a>.</p>

<h2 id="events-in-the-data-lake">Events in the Data Lake</h2>

<p>You may have noticed that the UI for the configuration of the TTL for event datasets in RTCP also offers another setting, the TTL for the <a href="https://www.pedromonjo.com/2023/05/data-lake.html">Data Lake</a>. The idea is the same as above: you define an expiration for the events in the Data Lake, after which the Data Lake will automatically delete old records. This parameter is not only useful to prevent your event datasets from growing indefinitely, but to comply with your regulatory mandates for data storage.</p>

<h2 id="anonymous-profiles">Anonymous Profiles</h2>

<p>Whenever a new identity is received by AEP, a profile is created and the profile count is increased. This means that all anonymous visitors to your website will increase the profile count, as each of them will have, at least an <a href="https://www.pedromonjo.com/2025/06/revisiting-ecid-service.html">ECID</a>. If an anonymous visitor never comes back or deletes their cookies, you have a profile in the system that is totally useless, but counts towards your license.</p>

<p>To solve this conundrum, AEP has a setting to delete old anonymous profiles: <a href="https://experienceleague.adobe.com/en/docs/experience-platform/profile/pseudonymous-profiles">Pseudonymous profiles data expiration</a>. You basically set an expiration, which is the period since the last visitor’s activity, after which the anonymous profile is deleted. This also means that, as long as an anonymous profile is active, it will be kept in the RTCP.</p>

<p>Let me clarify one detail. While I use the word “anonymous”, the documentation uses the word “pseudonymous”. I know there are subtle differences, but I will equate them and use my preferred word.</p>

<p>Finally, if you read this section and compare it with <a href="#events-in-rtcp">Events in RTCP</a>, you will notice that there seems to be some overlap. If you set the event dataset TTL, you should be able to delete anonymous profiles that are only composed of events. So, why is this other feature needed? I am not 100% sure. However, I can guess that an anonymous profile may contain more than just events, through other AEP features, in which case an event dataset TTL would not be enough. I will get more details and update the post.</p>

<h2 id="typical-values">Typical Values</h2>

<p>When setting these values in AEP, our customers ask us for help in setting them. There is no one-size-fits-all set of values. We need to review the use cases, the data, and the contractual obligations to make a suggestion. However, typical TTL values that we see among our customers are:</p>

<ul>
  <li>RTCP: 30, 60, 90 days.</li>
  <li>Data Lake: 13, 25 months.</li>
  <li>Pseudonymous profiles: 14, 30 days.</li>
</ul>

<p> </p>

<p><small>Photo by <a href="https://unsplash.com/@kwh101?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">K HOWARD</a> on <a href="https://unsplash.com/photos/a-wall-full-of-clocks-RaZacGzMCr8?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="Platform" /><summary type="html"><![CDATA[In my post on Individual Profile Deletion I explained a surgical approach to deleting profiles. This approach requires you to actively request the deletion of the data. With the Time-to-Live (TTL) configuration parameters, you can let the Adobe Experience Platform (AEP) take care of the deletion for you in certain scenarios. You set it and forget it.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-09-13-deletion-using-ttl/time.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-09-13-deletion-using-ttl/time.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Individual Profile Deletion</title><link href="https://www.pedromonjo.com/2026/08/individual-profile-deletion.html" rel="alternate" type="text/html" title="Individual Profile Deletion" /><published>2026-08-30T00:00:00-04:00</published><updated>2026-08-30T00:00:00-04:00</updated><id>https://www.pedromonjo.com/2026/08/individual-profile-deletion</id><content type="html" xml:base="https://www.pedromonjo.com/2026/08/individual-profile-deletion.html"><![CDATA[<p>This is my second post in the series on <a href="https://www.pedromonjo.com/2026/08/deletion-aep.html">Deletion in AEP</a>. Today I am going to talk about the two approaches you can take to delete individual profiles: Privacy Service and Data Lifecycle Management. These two methods allow you to surgically delete individual profiles.</p>

<!--more-->

<h2 id="some-context">Some Context</h2>

<p>Before getting into the details of each option, I want to provide some additional context so that you understand what you are actually deleting.</p>

<p>The first and most important concept for this post is the <a href="https://www.pedromonjo.com/2023/05/real-time-customer-profile.html">profile fragment</a>. As I have explained multiple times, there is no such thing as a <em>profile</em> in the Adobe Experience Platform (AEP). Therefore, there is no way to delete a profile. Instead, you have to request the deletion of the fragments that compose a profile. All fragments have a primary identity, which is what you need to supply in the requests. That being said, from now on, I will refer to profile instead of fragments, as the goal is usually to delete all fragments that compose a profile.</p>

<p>The second comment I want to make is that, even though the two methods presented in this post look very similar, they are not interchangeable. We have had customers who have used the Privacy Service as a method to delete old profiles, but this is not the right approach. I have to admit that I do not know why there are two different methods, when, from the outside, both look the same. My assumption is that the internal processes are different, especially knowing that the Privacy Service carries legal implications.</p>

<p>Finally, the triggers for the two services are very different. With the Privacy Service, it is the end user who starts the request, whereas with the Data Lifecycle Management, it is you who decides when to invoke it.</p>

<h2 id="privacy-service">Privacy Service</h2>

<p>Multiple countries around the world have enacted laws to protect the privacy of their citizens. Typical examples are the California Consumer Privacy Act (CCPA) and the General Data Protection Regulation (GDPR). We could debate for hours whether these laws are fair, whether they make any sense in today’s world, or whether they are even enforceable. What these laws have in common is that the responsibility lies with the owner of the data, not Adobe. Adobe is only the data processor, working on behalf of our customers, and all we can offer is a mechanism to comply with the law.</p>

<p>The <a href="https://experienceleague.adobe.com/en/docs/experience-platform/privacy/home">Privacy Service</a> is Adobe’s response to these laws. It is a tool “<em>[…] to automate compliance with data privacy regulations which, when violated, can result in major fines and disrupt data operations for your business</em>”. As explained in the previous section, the Privacy Service should only be invoked when the consequences involve legal penalties. Although we are talking here about deletion, it can also be used to retrieve customer data.</p>

<p>This service offers both an <a href="https://developer.adobe.com/experience-platform-apis/references/privacy-service">API</a> and a <a href="https://experienceleague.adobe.com/en/docs/experience-platform/privacy/ui/overview">Web UI</a>. Both render the same results. In fact, the UI calls the API behind the scenes.</p>

<p>As you read the documentation of this service, you will notice that it is for all Adobe tools that store customer data; it is not AEP-specific. So, when you invoke this service, you have to select which tools you want the service to access or delete the data from. This is the <a href="https://experienceleague.adobe.com/en/docs/experience-platform/privacy/api/appendix#accepted-product-values">full list of supported products</a>. In the case of AEP, you will likely want to select <code class="language-plaintext highlighter-rouge">profileService</code>, <code class="language-plaintext highlighter-rouge">aepDataLake</code>, and <code class="language-plaintext highlighter-rouge">identity</code>.</p>

<p>I want to conclude by clarifying that Privacy Service is <strong>not</strong> an end user interface for your customers to self-serve. You have to develop the interface to capture your customer’s request, log it, and process it. I strongly suggest that you work with your lawyers to guide the implementation of this process. They will tell you how this interface needs to be designed to comply with your local regulations. Typically, the request will come through a form in your own website (we offer a <a href="https://experienceleague.adobe.com/en/docs/experience-platform/privacy/js-library">JavaScript library</a> to help with this case), a third-party tool, or a call center, but I am sure that other channels could be included (WhatsApp comes to mind). Then, at some point during the processing of this request, you will invoke the Privacy Service, either manually or programmatically.</p>

<h2 id="data-lifecycle-management">Data Lifecycle Management</h2>

<p>For all other cases where you need to delete a limited number of individual profiles, you must use the <a href="https://experienceleague.adobe.com/en/docs/experience-platform/data-lifecycle/home">Data Lifecycle Management</a>. Given that AEP’s cost model is based on the number of profiles and the storage used in the Data Lake, it is a best practice to create a batch process where you list profiles that need to be deleted and send them regularly to AEP.</p>

<p>Typical use cases include:</p>

<ul>
  <li>Customers who have not interacted with your company for more than X months.</li>
  <li>Customers who are in a delinquent state and you should not send them any marketing material.</li>
  <li>Customers who have opted out of all marketing messages.</li>
</ul>

<p>As with the Privacy Service, the Data Lifecycle Management offers the <a href="https://experienceleague.adobe.com/en/docs/experience-platform/data-lifecycle/home#ui">Data Lifecycle UI workspace</a> and the <a href="https://experienceleague.adobe.com/en/docs/experience-platform/data-lifecycle/home#api">Data Hygiene API</a>. The name of the UI and the API are not the same because each offers slightly different functionalities. In the context of this post, I am only referring to the profile deletion feature that both support.</p>

<p>There are <a href="https://experienceleague.adobe.com/en/docs/experience-platform/data-lifecycle/ui/record-delete#quotas">limits in how many profiles can be requested for deletion per time period</a>. You will notice that these limits depend on whether you have the Privacy and Security Shield add-ons or not. These add-ons are offered mainly for regulated industries, although any Adobe customer can purchase them.</p>

<p>I am sure many of you are asking where the profiles are being deleted from. As with the Privacy Service, you can select the products. However, there is no consistency in the naming, so this API uses values like <code class="language-plaintext highlighter-rouge">datalake</code>, <code class="language-plaintext highlighter-rouge">identity</code>, <code class="language-plaintext highlighter-rouge">profile</code>, and <code class="language-plaintext highlighter-rouge">ajo</code>.</p>

<p>In future posts I will explain other functionalities of the Data Lifecycle Management and how to delete profiles if you need to go beyond the monthly limits of the Data Lifecycle Management.</p>

<p> </p>

<p><small>Photo by <a href="https://unsplash.com/@jdent?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Jason Dent</a> on <a href="https://unsplash.com/photos/a-black-and-white-photo-of-a-sign-that-says-privacy-please-JFk0dVyvdvw?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="Platform" /><summary type="html"><![CDATA[This is my second post in the series on Deletion in AEP. Today I am going to talk about the two approaches you can take to delete individual profiles: Privacy Service and Data Lifecycle Management. These two methods allow you to surgically delete individual profiles.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-08-30-individual-profile-deletion/privacy.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-08-30-individual-profile-deletion/privacy.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Deletion in AEP</title><link href="https://www.pedromonjo.com/2026/08/deletion-aep.html" rel="alternate" type="text/html" title="Deletion in AEP" /><published>2026-08-16T00:00:00-04:00</published><updated>2026-08-16T00:00:00-04:00</updated><id>https://www.pedromonjo.com/2026/08/deletion-aep</id><content type="html" xml:base="https://www.pedromonjo.com/2026/08/deletion-aep.html"><![CDATA[<p>This is the first post of a series where I will tackle one of the most controversial features in the Adobe Experience Platform (AEP): deletion of data in AEP. I will explain the background and the different options that you have to remove data from AEP.</p>

<!--more-->

<p>Initially, one may think that issuing a <code class="language-plaintext highlighter-rouge">DELETE</code> SQL command is standard and should be easy to implement in AEP. However, as I have mentioned multiple times, AEP is not based on a Relational Database Management System (RDBMS). I will not get into the details, but this is something that differentiates AEP from other Customer Data Platforms, and why I believe AEP is superior. The downside is that deleting data becomes more complex.</p>

<p>Let me bring once more AEP’s reference architecture, as it will be useful in this post:</p>

<p><a href="https://www.pedromonjo.com/assets/images/common/aep-reference-architecture.svg" data-toggle="lightbox"><img src="https://www.pedromonjo.com/assets/images/common/aep-reference-architecture.svg" alt="AEP Reference Architecture" width="300" /></a></p>

<h2 id="finding-the-profile">Finding the Profile</h2>

<p>In my post on <a href="https://www.pedromonjo.com/2025/04/profile-projection.html">profile projection</a>, I explained in great detail that there is no such thing as a “profile”. In fact, for a given identity, you may get different profile projections depending on the <a href="https://www.pedromonjo.com/2025/10/profile-merge-policies.html">merge policy</a> you choose. Here you can see the key of the difficulties in deleting profiles: you have to start by defining what profile you want to delete.</p>

<p>The first thing to consider is the identity. The <a href="https://www.pedromonjo.com/2025/11/ups-uis.html#unified-identity-service">Unified Identity Service</a> links identities in a graph. Are you interested in deleting all fragments associated with all identities in an identity graph, or only fragments associated with a single identity? Even more, do you want to delete the graph too?</p>

<p>The second step is to evaluate, once you have decided on identities, what fragments in the <a href="https://www.pedromonjo.com/2025/11/ups-uis.html#unified-profile-service">Unified Profile Service</a> compose the profile you want to delete and whether they are attributes or events. Each type of fragment behaves differently and has different options to delete them.</p>

<p>Finally, profile data lives in two places: the <a href="https://www.pedromonjo.com/2023/05/real-time-customer-profile.html">Real-Time Customer Profile</a> and the <a href="https://www.pedromonjo.com/2023/05/data-lake.html">Data Lake</a>. Given that these AEP building blocks are so different, deleting the data from them will require diverse methods. Do you want to delete the data from both locations or only one?</p>

<h2 id="deletion-options">Deletion Options</h2>

<p>I hope it is now clear that the very concept of “deletion” is not as clear as one would initially think and, consequently, AEP does not have one universal “delete profile” operation. The correct method depends on:</p>

<ul>
  <li><em>What must be removed</em>: Unified Profile Store data, Data Lake records, Unified Identity Service linkages, or all three.</li>
  <li><em>The deletion scope</em>: one profile, a set of identities, a dataset, or anonymous profiles based on retention.</li>
  <li><em>The reason</em>: legal privacy request, data correction, data minimization, retention management, or removal of an incorrectly loaded dataset.</li>
</ul>

<p>In the coming posts I will explain the different methods to delete data, based on the answers to the previous questions:</p>

<table class="table table-bordered">
  <thead>
    <tr>
      <th>Method</th>
      <th>Requirement</th>
      <th>Main scope</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Privacy Service</td>
      <td>GDPR, CCPA, or other data-subject erasure</td>
      <td>Unified Identity Service, Unified Profile Service, and optionally Data Lake</td>
    </tr>
    <tr>
      <td>Data Lifecycle record deletion</td>
      <td>Remove known customer IDs for cleansing or minimization</td>
      <td>Records from selected datasets, impacting Unified Profile Service and Unified Identity Service</td>
    </tr>
    <tr>
      <td>Profile system jobs API</td>
      <td>Correct Profile Store data without deleting the source data</td>
      <td>Unified Profile Service only</td>
    </tr>
    <tr>
      <td>Dataset deletion or scheduled dataset expiration</td>
      <td>Remove an entire bad, test, or retired source</td>
      <td>Data Lake, Unified Profile Service, and Unified Identity Service</td>
    </tr>
    <tr>
      <td>Pseudonymous Profile expiration</td>
      <td>Automatically remove old anonymous profiles</td>
      <td>Eligible anonymous profiles and related events</td>
    </tr>
    <tr>
      <td>Experience Event expiration</td>
      <td>Automatically remove old behavioral events</td>
      <td>Experience Events; not necessarily profile attributes</td>
    </tr>
    <tr>
      <td>Batch deletion</td>
      <td>Remove only a processed ingestion batch</td>
      <td>Data Lake only; insufficient for Unified Profile Service deletion</td>
    </tr>
  </tbody>
</table>

<p> </p>

<p><small><a href="https://www.pexels.com/photo/red-notebook-and-office-supplies-on-a-pink-surface-6193758/">Photo by KATRIN  BOLOVTSOVA</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="Platform" /><summary type="html"><![CDATA[This is the first post of a series where I will tackle one of the most controversial features in the Adobe Experience Platform (AEP): deletion of data in AEP. I will explain the background and the different options that you have to remove data from AEP.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-08-16-deletion-aep/delete.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-08-16-deletion-aep/delete.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The SNAPSHOT Clause</title><link href="https://www.pedromonjo.com/2026/07/the-snapshot-clause.html" rel="alternate" type="text/html" title="The SNAPSHOT Clause" /><published>2026-07-26T00:00:00-04:00</published><updated>2026-07-26T00:00:00-04:00</updated><id>https://www.pedromonjo.com/2026/07/the-snapshot-clause</id><content type="html" xml:base="https://www.pedromonjo.com/2026/07/the-snapshot-clause.html"><![CDATA[<p>In my previous post on <a href="https://www.pedromonjo.com/2026/07/data-distiller-query-optimization.html">Data Distiller Optimization</a>, I finished by mentioning the <code class="language-plaintext highlighter-rouge">SNAPSHOT</code> clause. In this post, I will explain in more detail how to use this Data Distiller feature. This will be a deep technical post.</p>

<!--more-->

<h2 id="additional-context">Additional Context</h2>

<p>Before going into the particulars of this post, I want to clarify a few important details:</p>

<ul>
  <li>I will assume that you have already read the previous post. If you have not, I highly recommend that you do so first to get the full context.</li>
  <li>This post will be largely based on the official documentation page for <a href="https://experienceleague.adobe.com/en/docs/experience-platform/query/key-concepts/incremental-load">Incremental load in Query Service</a>. The information in the Experience League is sometimes difficult to follow, so my goal here is to make it more digestible. For example, I do not find any obvious connection between this help page and the documentation of the <a href="https://experienceleague.adobe.com/en/docs/experience-platform/query/sql/syntax#snapshot-clause"><code class="language-plaintext highlighter-rouge">SNAPSHOT</code> clause</a>.</li>
  <li>Throughout this post, I will use the word “table”. However, remember that there are no such database tables in the Adobe Experience Platform (AEP). The underlying data is stored in a <a href="https://www.pedromonjo.com/2022/08/datasets.html">dataset</a>, as usual.</li>
  <li>The <code class="language-plaintext highlighter-rouge">SNAPSHOT</code> clause and, therefore, this post only applies to scheduled queries.</li>
</ul>

<h2 id="auxiliary-table-creation">Auxiliary Table Creation</h2>

<p>The first step is to create an auxiliary table to keep track of the snapshot IDs. We will use the same table for all queries.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">DROP</span> <span class="k">TABLE</span> <span class="n">IF</span> <span class="k">EXISTS</span> <span class="n">checkpoint_log</span><span class="p">;</span>
<span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">checkpoint_log</span> <span class="k">AS</span>
    <span class="k">SELECT</span>
        <span class="k">cast</span><span class="p">(</span><span class="k">NULL</span> <span class="k">AS</span> <span class="n">string</span><span class="p">)</span> <span class="n">process_name</span><span class="p">,</span>
        <span class="k">cast</span><span class="p">(</span><span class="k">NULL</span> <span class="k">AS</span> <span class="n">string</span><span class="p">)</span> <span class="n">process_status</span><span class="p">,</span>
        <span class="k">cast</span><span class="p">(</span><span class="k">NULL</span> <span class="k">AS</span> <span class="n">string</span><span class="p">)</span> <span class="n">last_snapshot_id</span><span class="p">,</span>
        <span class="k">cast</span><span class="p">(</span><span class="k">NULL</span> <span class="k">AS</span> <span class="nb">TIMESTAMP</span><span class="p">)</span> <span class="n">process_timestamp</span>
    <span class="k">WHERE</span> <span class="k">false</span><span class="p">;</span>
</code></pre></div></div>

<p>Be very careful with this query. If you accidentally run it a second time in production, the first command <code class="language-plaintext highlighter-rouge">DROP TABLE IF EXISTS checkpoint_log;</code> will delete the table and, with it, all existing data.</p>

<h2 id="initialization">Initialization</h2>

<p>The query that I will show in the next section expects an initial value in the table. For every such query, you need to insert a row in the table:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">checkpoint_log</span>
    <span class="k">SELECT</span>
        <span class="s1">'DIM_TABLE_ABC'</span> <span class="n">process_name</span><span class="p">,</span>
        <span class="s1">'SUCCESSFUL'</span> <span class="n">process_status</span><span class="p">,</span>
        <span class="k">cast</span><span class="p">(</span><span class="k">NULL</span> <span class="k">AS</span> <span class="n">string</span><span class="p">)</span> <span class="n">last_snapshot_id</span><span class="p">,</span>
        <span class="k">CURRENT_TIMESTAMP</span> <span class="n">process_timestamp</span><span class="p">;</span>
</code></pre></div></div>

<p>You must replace <code class="language-plaintext highlighter-rouge">DIM_TABLE_ABC</code> with a unique name for your query. There is no need to use the name of the dataset you are querying, you can use any other unique text for your query, as long as you use it consistently. In fact, if you are going to run multiple queries against the same dataset, I do not recommend that you use the dataset name, but a different name for each query.</p>

<p>As with the table creation, you should not run this query a second time for the same <code class="language-plaintext highlighter-rouge">process_name</code> in production. It will add a new entry with the current timestamp and all previous entries will be ignored. The effective consequence is that the next run of the main query will start from the beginning of time.</p>

<h2 id="incremental-processing">Incremental Processing</h2>

<p>Now it is time to run your scheduled query. This is how it will look like:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">$$</span> <span class="k">BEGIN</span>
    <span class="k">SET</span> <span class="n">resolve_fallback_snapshot_on_failure</span><span class="o">=</span><span class="k">true</span><span class="p">;</span>
    <span class="k">SET</span> <span class="o">@</span><span class="n">from_snapshot_id</span> <span class="o">=</span> 
        <span class="k">SELECT</span> <span class="n">coalesce</span><span class="p">(</span><span class="n">last_snapshot_id</span><span class="p">,</span> <span class="s1">'HEAD'</span><span class="p">)</span> 
        <span class="k">FROM</span> <span class="n">checkpoint_log</span> <span class="n">a</span> 
        <span class="k">JOIN</span> <span class="p">(</span>
            <span class="k">SELECT</span> <span class="k">MAX</span><span class="p">(</span><span class="n">process_timestamp</span><span class="p">)</span><span class="n">process_timestamp</span> 
            <span class="k">FROM</span> <span class="n">checkpoint_log</span>
            <span class="k">WHERE</span> <span class="n">process_name</span> <span class="o">=</span> <span class="s1">'DIM_TABLE_ABC'</span> <span class="k">AND</span> <span class="n">process_status</span> <span class="o">=</span> <span class="s1">'SUCCESSFUL'</span> 
        <span class="p">)</span> <span class="n">b</span>
        <span class="k">ON</span> <span class="n">a</span><span class="p">.</span><span class="n">process_timestamp</span> <span class="o">=</span> <span class="n">b</span><span class="p">.</span><span class="n">process_timestamp</span><span class="p">;</span>
    <span class="k">SET</span> <span class="o">@</span><span class="n">to_snapshot_id</span> <span class="o">=</span> <span class="k">SELECT</span> <span class="n">snapshot_id</span> <span class="k">FROM</span> <span class="p">(</span><span class="k">SELECT</span> <span class="n">history_meta</span><span class="p">(</span><span class="s1">'DIM_TABLE_ABC'</span><span class="p">))</span> <span class="k">WHERE</span> <span class="n">is_current</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
    <span class="k">SET</span> <span class="o">@</span><span class="n">last_updated_timestamp</span> <span class="o">=</span> <span class="k">SELECT</span> <span class="k">CURRENT_TIMESTAMP</span><span class="p">;</span>
    
    <span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">DIM_TABLE_ABC_Incremental</span> <span class="k">AS</span>
        <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">DIM_TABLE_ABC</span> <span class="n">SNAPSHOT</span> <span class="k">BETWEEN</span> <span class="o">@</span><span class="n">from_snapshot_id</span> <span class="k">AND</span> <span class="o">@</span><span class="n">to_snapshot_id</span><span class="p">;</span>

    <span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">checkpoint_log</span>
        <span class="k">SELECT</span>
            <span class="s1">'DIM_TABLE_ABC'</span> <span class="n">process_name</span><span class="p">,</span>
            <span class="s1">'SUCCESSFUL'</span> <span class="n">process_status</span><span class="p">,</span>
            <span class="k">cast</span><span class="p">(</span><span class="o">@</span><span class="n">to_snapshot_id</span> <span class="k">AS</span> <span class="n">string</span><span class="p">)</span> <span class="n">last_snapshot_id</span><span class="p">,</span>
            <span class="k">cast</span><span class="p">(</span><span class="o">@</span><span class="n">last_updated_timestamp</span> <span class="k">AS</span> <span class="nb">TIMESTAMP</span><span class="p">)</span> <span class="n">process_timestamp</span><span class="p">;</span>

<span class="n">EXCEPTION</span>
    <span class="k">WHEN</span> <span class="n">OTHERS</span> <span class="k">THEN</span>
        <span class="k">SELECT</span> <span class="s1">'ERROR'</span><span class="p">;</span>

<span class="k">END</span>
<span class="err">$$</span><span class="p">;</span>
</code></pre></div></div>

<p>Let’s analyze this code. There is a lot going on in just 30 lines.</p>

<h3 id="anonymous-block">Anonymous block</h3>

<p>You need to insert your query in an <a href="https://experienceleague.adobe.com/en/docs/experience-platform/query/key-concepts/anonymous-block">anonymous block</a> using the keywords <code class="language-plaintext highlighter-rouge">BEGIN</code>, <code class="language-plaintext highlighter-rouge">EXCEPTION</code> and <code class="language-plaintext highlighter-rouge">END</code> to be able to exit gracefully in case of errors.</p>

<h3 id="expired-snapshots">Expired snapshots</h3>

<p>The documentation mentions that there could be problems with <a href="https://experienceleague.adobe.com/en/docs/experience-platform/query/key-concepts/incremental-load#expired-snapshots">expired snapshots</a>. I do not exactly know what they mean by this concept, and it is not clearly explained either.</p>

<p>In any case, the proposed solution is to add the line <code class="language-plaintext highlighter-rouge">SET resolve_fallback_snapshot_on_failure=true;</code> to the query. I have added it just in case, but I am not sure it is always needed.</p>

<h3 id="retrieve-the-last-snapshot-id">Retrieve the last snapshot ID</h3>

<p>Next, you need to get the last snapshot ID from the auxiliary table <code class="language-plaintext highlighter-rouge">checkpoint_log</code>.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">SET</span> <span class="o">@</span><span class="n">from_snapshot_id</span> <span class="o">=</span> 
        <span class="k">SELECT</span> <span class="n">coalesce</span><span class="p">(</span><span class="n">last_snapshot_id</span><span class="p">,</span> <span class="s1">'HEAD'</span><span class="p">)</span> 
        <span class="k">FROM</span> <span class="n">checkpoint_log</span> <span class="n">a</span> 
        <span class="k">JOIN</span> <span class="p">(</span>
            <span class="k">SELECT</span> <span class="k">MAX</span><span class="p">(</span><span class="n">process_timestamp</span><span class="p">)</span> <span class="n">process_timestamp</span> 
            <span class="k">FROM</span> <span class="n">checkpoint_log</span>
            <span class="k">WHERE</span> <span class="n">process_name</span> <span class="o">=</span> <span class="s1">'DIM_TABLE_ABC'</span> <span class="k">AND</span> <span class="n">process_status</span> <span class="o">=</span> <span class="s1">'SUCCESSFUL'</span> 
        <span class="p">)</span> <span class="n">b</span>
        <span class="k">ON</span> <span class="n">a</span><span class="p">.</span><span class="n">process_timestamp</span> <span class="o">=</span> <span class="n">b</span><span class="p">.</span><span class="n">process_timestamp</span><span class="p">;</span>
</code></pre></div></div>

<p>You need to replace <code class="language-plaintext highlighter-rouge">DIM_TABLE_ABC</code> with the unique name for your query that you decided when you <a href="#initialization">initialized the table</a>.</p>

<p>Now you have the variable <code class="language-plaintext highlighter-rouge">@from_snapshot_id</code> set to the snapshot ID of the last execution of the query. In the first run, it will be set to <code class="language-plaintext highlighter-rouge">NULL</code> thanks to the initialization.</p>

<h3 id="retrieve-the-current-snapshot-id">Retrieve the current snapshot ID</h3>

<p>Using the function <code class="language-plaintext highlighter-rouge">history_meta</code>, you need to get the last snapshot ID of the dataset <code class="language-plaintext highlighter-rouge">DIM_TABLE_ABC</code>. In this case, you have to replace this dataset name with your real dataset name, not the <code class="language-plaintext highlighter-rouge">process_name</code> value.</p>

<p>It took me a bit of time to understand why we need this value. I initially thought that we could just use <code class="language-plaintext highlighter-rouge">'TAIL'</code> but then I realized that there could be race conditions between the query execution and new batches: if a new batch arrives while the query is running, there could be a case where the new data would be skipped.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">SET</span> <span class="o">@</span><span class="n">to_snapshot_id</span> <span class="o">=</span> <span class="k">SELECT</span> <span class="n">snapshot_id</span> <span class="k">FROM</span> <span class="p">(</span><span class="k">SELECT</span> <span class="n">history_meta</span><span class="p">(</span><span class="s1">'DIM_TABLE_ABC'</span><span class="p">))</span> <span class="k">WHERE</span> <span class="n">is_current</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
</code></pre></div></div>

<h3 id="get-the-current-timestamp">Get the current timestamp</h3>

<p>To sort the entries in <code class="language-plaintext highlighter-rouge">checkpoint_log</code> the query relies on the timestamp of the execution of the query, so we need to retrieve it too.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">SET</span> <span class="o">@</span><span class="n">last_updated_timestamp</span> <span class="o">=</span> <span class="k">SELECT</span> <span class="k">CURRENT_TIMESTAMP</span><span class="p">;</span>
</code></pre></div></div>

<h3 id="your-query">Your query</h3>

<p>Now is when you finally run your complex query, probably dozens of lines long. The example above shows a very simple query as a placeholder:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">DIM_TABLE_ABC_Incremental</span> <span class="k">AS</span>
        <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">DIM_TABLE_ABC</span> <span class="n">SNAPSHOT</span> <span class="k">BETWEEN</span> <span class="o">@</span><span class="n">from_snapshot_id</span> <span class="k">AND</span> <span class="o">@</span><span class="n">to_snapshot_id</span><span class="p">;</span>
</code></pre></div></div>

<p>This can be any query. The fact that the tables are called <code class="language-plaintext highlighter-rouge">DIM_TABLE_ABC</code> and <code class="language-plaintext highlighter-rouge">DIM_TABLE_ABC_Incremental</code> is irrelevant and it has no connection with the value you have inserted into the auxiliary table. The only important relation here is that the <code class="language-plaintext highlighter-rouge">process_name</code> value in <code class="language-plaintext highlighter-rouge">checkpoint_log</code> and your query is 1:1. For every query you schedule, you need to come up with a new <code class="language-plaintext highlighter-rouge">process_name</code>.</p>

<p>The truly important part of the query is <code class="language-plaintext highlighter-rouge">SNAPSHOT BETWEEN @from_snapshot_id AND @to_snapshot_id</code>, which you will need to embed in your query in the right place. This tells the Query Service engine to only process the dataset entries between these two markers.</p>

<p>If you have to combine two or more datasets that need to also use snapshot IDs, you will have to replicate the code to manage them, using different variable names and <code class="language-plaintext highlighter-rouge">process_name</code> values for each dataset. I am not implying that you have to use it for all your datasets in a query. If you are combining a large dataset with a small one, you may not need to use the <code class="language-plaintext highlighter-rouge">SNAPSHOT</code> clause with the small dataset.</p>

<p>In general, this central body of the query will be developed in isolation before adding the rest of the surrounding code:</p>

<ol>
  <li>Start by developing and testing your query without the <code class="language-plaintext highlighter-rouge">SNAPSHOT</code> clause.</li>
  <li>Once your base query works, add the <code class="language-plaintext highlighter-rouge">SNAPSHOT</code> clause.</li>
  <li>Add some manual entries to <code class="language-plaintext highlighter-rouge">checkpoint_log</code> and test the query with the <code class="language-plaintext highlighter-rouge">SNAPSHOT</code> clause.</li>
  <li>Add the rest of the code around your main query and test it.</li>
  <li>Schedule your query and check for a few days that it works.</li>
</ol>

<p>Finally, you are ready to go to production:</p>

<ol>
  <li>Manually insert the <a href="#initialization">initialization entry</a>.</li>
  <li>Copy the query from development or staging to production.</li>
  <li>Schedule the query.</li>
</ol>

<p>It goes without saying that you should monitor it for a few days.</p>

<h3 id="update-the-auxiliary-table">Update the auxiliary table</h3>

<p>With the query successfully executed, you should insert the last snapshot ID into the auxiliary table:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">checkpoint_log</span>
        <span class="k">SELECT</span>
            <span class="s1">'DIM_TABLE_ABC'</span> <span class="n">process_name</span><span class="p">,</span>
            <span class="s1">'SUCCESSFUL'</span> <span class="n">process_status</span><span class="p">,</span>
            <span class="k">cast</span><span class="p">(</span><span class="o">@</span><span class="n">to_snapshot_id</span> <span class="k">AS</span> <span class="n">string</span><span class="p">)</span> <span class="n">last_snapshot_id</span><span class="p">,</span>
            <span class="k">cast</span><span class="p">(</span><span class="o">@</span><span class="n">last_updated_timestamp</span> <span class="k">AS</span> <span class="nb">TIMESTAMP</span><span class="p">)</span> <span class="n">process_timestamp</span><span class="p">;</span>
</code></pre></div></div>

<p>The value of <code class="language-plaintext highlighter-rouge">@to_snapshot_id</code> in the current execution will become the value of <code class="language-plaintext highlighter-rouge">@from_snapshot_id</code> in the next execution.</p>

<p>As I also explained in my previous post, there is no <code class="language-plaintext highlighter-rouge">UPDATE</code> operation with datasets. This is why you have to add the value to the end of the dataset (<code class="language-plaintext highlighter-rouge">INSERT</code>) and add a timestamp.</p>

<h3 id="finalization">Finalization</h3>

<p>I am not intimately familiar with exception handling in SQL. My understanding is that this code just returns <code class="language-plaintext highlighter-rouge">'ERROR'</code> if any query failed.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">EXCEPTION</span>
    <span class="k">WHEN</span> <span class="n">OTHERS</span> <span class="k">THEN</span>
        <span class="k">SELECT</span> <span class="s1">'ERROR'</span><span class="p">;</span>

<span class="k">END</span>
</code></pre></div></div>

<p> </p>

<p><small>Photo by <a href="https://unsplash.com/@creativemindsfactory?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Creative Minds Factory</a> on <a href="https://unsplash.com/photos/a-stylized-illustration-of-data-storage-and-processing-YLzJtu-Vmmo?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="Platform" /><summary type="html"><![CDATA[In my previous post on Data Distiller Optimization, I finished by mentioning the SNAPSHOT clause. In this post, I will explain in more detail how to use this Data Distiller feature. This will be a deep technical post.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-07-26-the-snapshot-clause/database.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-07-26-the-snapshot-clause/database.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Data Distiller Query Optimization</title><link href="https://www.pedromonjo.com/2026/07/data-distiller-query-optimization.html" rel="alternate" type="text/html" title="Data Distiller Query Optimization" /><published>2026-07-12T00:00:00-04:00</published><updated>2026-07-12T00:00:00-04:00</updated><id>https://www.pedromonjo.com/2026/07/data-distiller-query-optimization</id><content type="html" xml:base="https://www.pedromonjo.com/2026/07/data-distiller-query-optimization.html"><![CDATA[<p>I was recently working with a customer. They were planning to launch a marketing campaign and one of the steps involved writing a query in Data Distiller. As I watched our data architect provide some specific recommendations, I realized that there was a gap in the knowledge of the data engineers of this customer. This is not the first time I have seen avoidable issues with queries, so I decided to explain some tricks to keep in mind with Data Distiller.</p>

<!--more-->

<h2 id="data-lake-vs-rdbms">Data Lake vs RDBMS</h2>

<p>Although the Adobe Experience Platform (AEP) <a href="https://www.pedromonjo.com/2023/05/data-lake.html">Data Lake</a> has a SQL interface, it is far from being a Relational DataBase Management System (RDBMS). The technologies behind each are completely different. The fact that you can use a PostgreSQL client to connect to the Data Lake does not make it a PostgreSQL database.</p>

<p>There are two fundamental differences: file structure and data manipulation.</p>

<h3 id="file-structure">File structure</h3>

<p>Each RDBMS engine has its own file structure to store the data: SQLite uses a single file for everything, MariaDB spreads the data over multiple files, and Oracle requires its own partition or block device.</p>

<p>In the case of AEP, you will have already noticed that XDM is based on JSON. Dataset batches in AEP are stored as JSON files. Think of each of them as a JSON array containing multiple rows of data. This applies to both profile and event datasets.</p>

<h3 id="data-manipulation">Data manipulation</h3>

<p>RDBMS engines support various operations to manipulate data: <code class="language-plaintext highlighter-rouge">INSERT</code>, <code class="language-plaintext highlighter-rouge">UPDATE</code> or <code class="language-plaintext highlighter-rouge">DELETE</code>. When you issue a <code class="language-plaintext highlighter-rouge">DELETE</code>, the RDBMS may delete the data from disk, but it is more likely that it just sets a hidden flag to mark the row as deleted, so that it is ignored in subsequent queries. An <code class="language-plaintext highlighter-rouge">UPDATE</code> may modify the existing value on disk or create a new row with the new data, marking the previous row as deleted; in both cases, the old data becomes unavailable.</p>

<p>The AEP Data Lake only supports <code class="language-plaintext highlighter-rouge">INSERT</code> operations. Once a profile fragment or event is written into a dataset, it is never modified or deleted. The only way to delete data from the Data Lake is to delete a batch or the whole dataset, or when a data retention policy rule determines that some data has to be deleted. In other words, all data that you send to AEP keeps piling up in the Data Lake.</p>

<p>Do not get confused with the <a href="https://www.pedromonjo.com/2023/05/real-time-customer-profile.html">Real-Time Customer Profile</a> (RTCP). The RTCP only contains the last version of the profile and the events that have not expired due to TTL. So, when you send a new profile fragment, RTCP will delete the previous fragment and only keep the last one, while the Data Lake will keep all versions.</p>

<h2 id="querying-the-data-lake">Querying the Data Lake</h2>

<p>I am sure you know where I am going with this, but I want to make sure we are all on the same page. When I run <code class="language-plaintext highlighter-rouge">SELECT * FROM my_fancy_dataset</code>, the Query Service will process <em>all</em> rows in the dataset from the beginning of time. Think about it: if you have web events from a website with heavy traffic from the last 2 years, Query Service will process billions of rows. And if you think that you are safe with querying the profile snapshot, you should remember that the dataset has the profile snapshots from the past too.</p>

<p>No wonder that the query that ran smoothly a year ago, when you had just finished your AEP’s implementation, is dragging its feet now. The slowdown is exponential and you are consuming compute hours like crazy.</p>

<h2 id="query-optimization">Query Optimization</h2>

<p>I hope that, by now, you are convinced that all queries that need to run regularly must be optimized. This should not be an afterthought; your query should be optimized from the beginning, even if it is not needed initially. Modifying a query in production is a risky and expensive operation. In the long term, it is cheaper to write the query thinking about the future from the get-go.</p>

<p>Here you have a few techniques to optimize the execution of queries.</p>

<h3 id="the-where-clause">The WHERE clause</h3>

<p>As simple as it seems, you should use time-based <code class="language-plaintext highlighter-rouge">WHERE</code> clauses wherever possible. If you are querying the profile snapshot, you only need last day’s data. If you are processing events, I am sure that the events from 2 years ago are not relevant anymore.</p>

<h3 id="derived-tables">Derived tables</h3>

<p>Some datasets are huge by nature. My best example is the <code class="language-plaintext highlighter-rouge">journey_step_events</code> dataset, which has every step of every journey for all profiles in journeys. Another example could be the web event dataset of a heavy-traffic website, as mentioned above.</p>

<p>If you are going to run complex queries based on this huge dataset, but do not need all the data in it, my strong recommendation is to <a href="https://experienceleague.adobe.com/en/docs/experience-platform/query/data-distiller/derived-datasets/create-derived-datasets-with-sql">create a derived dataset</a> using a Create Table As Select (CTAS) query. In this initial query, you only select the rows and the columns that you really need. Then, you run your complex query on this derived dataset.</p>

<h3 id="the-snapshot-clause">The SNAPSHOT clause</h3>

<p>This is probably the most important feature for query optimization, and the most important section of this post. Data Distiller offers the <a href="https://experienceleague.adobe.com/en/docs/experience-platform/query/sql/syntax#snapshot-clause"><code class="language-plaintext highlighter-rouge">SNAPSHOT</code> clause</a> to tell the query engine to only process rows between two markers. It is explained in great detail in Experience League: <a href="https://experienceleague.adobe.com/en/docs/experience-platform/query/key-concepts/incremental-load">Incremental load in Query Service</a>. However, I am sure that by just looking at the title of that page many will have skipped it, and this is why I am highlighting it here.</p>

<p>I want to clarify that this feature only makes sense if you are scheduling a query to run regularly (every hour, every day, every week…). You do not need it if you are just running a query once or need to process all data in the dataset.</p>

<p>The idea is fairly simple, although the code may look complex. You start by creating and initializing an auxiliary table to keep track of the last marker that has been processed. Then, with every execution:</p>

<ol>
  <li>Get the previous marker from the auxiliary table.</li>
  <li>Get the last marker in the dataset.</li>
  <li>Run your query between these two markers.</li>
  <li>Insert the last marker into the auxiliary table for the next run.</li>
</ol>

<p>Since this post is already getting too long, and this feature is so important, I will continue in my next post on <a href="https://www.pedromonjo.com/2026/07/the-snapshot-clause.html">the <code class="language-plaintext highlighter-rouge">SNAPSHOT</code> clause</a>.</p>

<p>Finally, by no means am I an expert in Data Distiller. If you feel like I am missing an optimization trick, let me know in the comments.</p>

<p> </p>

<p><small><a href="https://www.pexels.com/photo/black-car-instrument-panel-cluster-3791193/">Photo by Diana ✨</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="Platform" /><summary type="html"><![CDATA[I was recently working with a customer. They were planning to launch a marketing campaign and one of the steps involved writing a query in Data Distiller. As I watched our data architect provide some specific recommendations, I realized that there was a gap in the knowledge of the data engineers of this customer. This is not the first time I have seen avoidable issues with queries, so I decided to explain some tricks to keep in mind with Data Distiller.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-07-12-data-distiller-query-optimization/speed.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-07-12-data-distiller-query-optimization/speed.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">What Is and What Is Not a DAM</title><link href="https://www.pedromonjo.com/2026/06/what-is-what-is-not-dam.html" rel="alternate" type="text/html" title="What Is and What Is Not a DAM" /><published>2026-06-21T00:00:00-04:00</published><updated>2026-06-21T00:00:00-04:00</updated><id>https://www.pedromonjo.com/2026/06/what-is-what-is-not-dam</id><content type="html" xml:base="https://www.pedromonjo.com/2026/06/what-is-what-is-not-dam.html"><![CDATA[<p>I was visiting a customer a couple of weeks ago. One of the topics we discussed was the number of Digital Asset Management (DAM) tools that they had. They were asking us for a consolidation from all those DAMs to Adobe Experience Manager (AEM) Assets. On the surface, it looked like a quick win. However, after scratching that surface a bit, we realized that the request was more complex than that.</p>

<!--more-->

<h2 id="storage-vs-dam">Storage vs DAM</h2>

<p>The first step is to define what a DAM is. Duckduckgo’s LLM, based on Adobe and Gartner, defines it as:</p>

<blockquote>
  <p>Digital asset management is the process of organizing, storing, and distributing digital files using specialized software, which helps create a centralized repository for easy access and management of digital assets like images, videos, and documents. It enhances efficiency, security, and accessibility while ensuring consistent use of assets across an organization.</p>
</blockquote>

<p>This definition emphasizes the enterprise focus of the process and tools. It is not just a simple storage, like your hard drive or OneDrive. It does much more. One key feature (or, rather, lack of) is that your DAM should not be your working directory.</p>

<h2 id="use-cases">Use Cases</h2>

<p>In order to select the right tool to store content, you need to first define the use case. Let’s see the most common.</p>

<h3 id="image-and-video-creation">Image and Video Creation</h3>

<p>When a photographer is taking photos or a videographer is shooting a video, you are not expecting the content to be stored directly in a corporate storage solution. Maybe in the future, with mobile or satellite networks supporting gigabits per second, this type of content can be directly uploaded to the cloud. For now, it will go to a storage device like an SD card or a portable SSD.</p>

<p>You may be thinking that I am going too far with this type of storage. However, it still must be managed. If I am paying for these photos, I want to make sure that they are properly managed.</p>

<h3 id="content-editing">Content Editing</h3>

<p>I will not entertain any type of local or network storage. In 2026 and in a corporate environment, I am not sure they have a purpose for content management.</p>

<p>Once you have the photos or the videos, it is time to edit them; in the case of text, this is where you start from. You need some type of storage where you can store, edit, and share the content. This storage should allow you and relevant people to apply changes, add comments, or approve it. It needs to be compatible with the editing software that you are using. You do not want to copy a file from A to B, edit it in B, and move it back to A. Even worse, sending emails around with the content attached. Here be dragons.</p>

<p>For example, with Adobe, if you are going to use Frame.io and Adobe Photoshop to edit your content, Adobe cloud storage directly integrates with them.</p>

<p>Types of storage that you can use in this case:</p>

<ul>
  <li>Cloud providers: SharePoint, OneDrive, Google Drive, Adobe Document Cloud, and similar; they all allow content sharing.</li>
  <li>Project tool storage: as you mature in <a href="https://www.pedromonjo.com/2023/10/content-supply-chain.html">Content Supply Chain</a>, you will want to use project management tools like Workfront, which come with their own storage.</li>
</ul>

<h3 id="content-publishing">Content Publishing</h3>

<p>Once content is approved, it is finally ready for your marketing campaigns. Now the situation changes; the storage tools from above are usually not fit for purpose. Instead, you need a tool that allows your campaign managers to find and use the approved content, and makes it available to other campaign tools.</p>

<p>This is where a DAM comes into play, the whole reason for its existence. While other types of storage are meant to keep lots of content, a DAM should only contain content that is ready to be shown to customers and the rest of the world. From hundreds of photos that you have taken at the beginning of the process, only those edited and approved should be in the DAM. In other words, a DAM is not a good place to store temporary content that is still being edited, or a storage dump to keep everything.</p>

<p>In the case of <a href="https://experienceleague.adobe.com/en/docs/experience-manager-learn/assets/overview">Adobe Experience Manager Assets</a>, these are some of the features you get:</p>

<ul>
  <li>A folder structure and tags for easy management and search. You do not want to spend time finding the right image.</li>
  <li>Various renditions for images, to adjust to different screens and locations.</li>
  <li>Integrations with:
    <ul>
      <li>AEM Sites (obviously!)</li>
      <li>Adobe Journey Optimizer for email, SMS and push notifications.</li>
      <li>Marketo.</li>
      <li>Workfront, to receive the approved assets.</li>
    </ul>
  </li>
</ul>

<h3 id="archival">Archival</h3>

<p>Creating all this content does not come cheap. As an enterprise, it forms part of your intellectual property, and you may want to keep everything that has been created, even if it has never been used. For this purpose, you will want to use another type of storage, specialized for archival. You are not likely to use this content anymore, so there is no need to make it easily accessible.</p>

<p>You could continue using your cloud provider, but there is another solution to consider: long-term, secure, and durable storage. They tend to be cheaper than cloud storage, as they have been optimized for storage, not retrieval. Some examples are AWS Glacier, Azure Archive Blob and Google Cloud Nearline.</p>

<h2 id="final-remarks">Final Remarks</h2>

<p>I hope that it is now clear when and when not to use a DAM. In summary, a DAM has been designed to store published content, content that has to be accessible by multiple people, from campaign managers to end users. For all other cases, you should be looking at a solution that meets your needs.</p>

<p> </p>

<p><small><a href="https://www.pexels.com/photo/white-and-black-picture-frame-4340919/">Photo by Taryn Elliott</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="Content" /><summary type="html"><![CDATA[I was visiting a customer a couple of weeks ago. One of the topics we discussed was the number of Digital Asset Management (DAM) tools that they had. They were asking us for a consolidation from all those DAMs to Adobe Experience Manager (AEM) Assets. On the surface, it looked like a quick win. However, after scratching that surface a bit, we realized that the request was more complex than that.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-06-21-what-is-what-is-not-dam/photo-collection.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-06-21-what-is-what-is-not-dam/photo-collection.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Bad Data Quality or Bad Data Governance</title><link href="https://www.pedromonjo.com/2026/06/bad-data-quality-bad-data-governance.html" rel="alternate" type="text/html" title="Bad Data Quality or Bad Data Governance" /><published>2026-06-07T00:00:00-04:00</published><updated>2026-06-07T00:00:00-04:00</updated><id>https://www.pedromonjo.com/2026/06/bad-data-quality-bad-data-governance</id><content type="html" xml:base="https://www.pedromonjo.com/2026/06/bad-data-quality-bad-data-governance.html"><![CDATA[<p>I have spent the last five years of my career at Adobe, working with the <a href="https://www.pedromonjo.com/category/platform/">Adobe Experience Platform</a> (AEP). I cannot say “I have seen it all” (I wish I could), but I have seen many different implementations. It is thanks to this experience that I can share with you the content of this blog. While the majority of my customers have had great data to start with, from time to time I have heard the concern that their data is of bad quality and they need to clean it up before sending it to AEP.</p>

<!--more-->

<h2 id="bad-quality">Bad Quality</h2>

<p>Let’s start by defining what we mean by <em>bad data quality</em>. <a href="https://www.ibm.com/think/insights/cost-of-poor-data-quality">IBM defines</a> it as follows:</p>

<blockquote>
  <p>Poor-quality data occurs when datasets fail to meet the requirements of a specific business operation. Even data that appears accurate and complete can function as “bad data” if it isn’t fit for purpose, meaning it doesn’t support the use case […]</p>
</blockquote>

<p>I very much like the inclusion of “business operation” and “use case” in the definition. Data is data is data. Zeroes and ones are not wrong, unless there is data corruption. The quality of the data has to be evaluated against the desired outcomes of the usage of that data. In our case of the digital marketing sphere, data is good only if a marketer can trust it to make decisions and run successful campaigns.</p>

<p>The consequences of having poor data quality are multiple and varied. It would require a long post, which nobody would read, and it would still be incomplete. Instead, let me share a couple of examples in AEP:</p>

<ul>
  <li><em>Segmentation</em>. You want to be sure that the audience receiving the marketing message is the right one. Sending a message that does not resonate with a recipient is a waste of resources and can damage reputation.</li>
  <li><em>Personalization</em>. In this world of hyper-personalization, generic or incorrect messages ruin campaigns.</li>
</ul>

<h2 id="data-patching">Data Patching</h2>

<p>Once the bad data is in the system and is needed immediately, we have no other option than to patch it. However, this is just a band-aid, as the next batch will contain errors again. I want to emphasize that this is a short-term solution. It just means that we are doing what we can to keep the system working, but not fixing the root problem.</p>

<p>I will not try to enumerate all options to clean up the data temporarily, just share some ideas with AEP in mind:</p>

<ul>
  <li>Use <a href="https://experienceleague.adobe.com/en/docs/experience-platform/data-prep/functions">data prep functions</a> upon ingestion, even though we strongly recommend against using them.</li>
  <li>Use <a href="https://experienceleague.adobe.com/en/docs/experience-platform/query/data-distiller/overview">data distiller</a> to:
    <ul>
      <li>Modify profiles with issues that can easily be fixed.</li>
      <li>Mark profiles that should not be used, or put them in an exclusion audience.</li>
      <li>Calculate a data quality index per profile, and create an exclusion audience based on a data quality index below X.</li>
    </ul>
  </li>
  <li>In Adobe Journey Optimizer:
    <ul>
      <li>Use conditions to exclude profiles that do not meet the minimum standards for a journey.</li>
      <li>Use an <a href="https://experienceleague.adobe.com/en/docs/journey-optimizer/using/orchestrate-journeys/about-journey-building/update-profiles">update profile activity</a> to increase the quality of the profile.</li>
    </ul>
  </li>
</ul>

<h2 id="bad-governance">Bad Governance</h2>

<p>In the past, when I heard my customers complain about their low data quality, I just accepted it. It is not my job to judge how they run their business.</p>

<p>However, recently, someone mentioned that the problem is not with data quality, but with data governance. It was one of those lightbulb moments 💡. I told myself: How could I not have thought of it? It makes a lot of sense!</p>

<p>Poor data quality is the evidence of the problem, not the problem itself. The problem lies in allowing the wrong data to enter the system in the first place. Why did it happen?</p>

<h2 id="data-governance-for-data-quality">Data Governance for Data Quality</h2>

<p>Many years ago, I had a customer with a problem in the data they received from third parties. These third parties were using the email address as the identity, which, as you all know, is a bad, bad idea. However, my customer wanted AEP to fix the problem. Before AEP, they had a complex solution based on heuristic rules to choose which profile an event should be assigned to. It took me a lot of effort convincing them that <a href="https://www.pedromonjo.com/2024/02/cdp-vs-mdm.html">a CDP is not an MDM</a>. Finally, they realized that the solution was to force all customers to have a unique email address.</p>

<p>In order to ensure that any data entering the system is of sufficient quality, you have to make sure that humans enter correct data. In other words, it is a human responsibility, as, ultimately, all data comes from human sources.</p>

<p>Therefore, data governance is the only solution that addresses the root of the problem: the human. I am not saying that humans are stupid, but we know that we all make mistakes. These mistakes can be as simple as a typo or as important as not submitting the data. You will have noticed that I have used the word “solution” here, whereas I used the word “patching” earlier. I have done it on purpose.</p>

<p>While researching for this topic, I found the Wikipedia page on <a href="https://en.wikipedia.org/wiki/Data_governance">Data Governance</a> very useful. I want to highlight the following comments:</p>

<blockquote>
  <p>Data governance is the set of principles, policies, and processes that guide the effective and responsible use of data within an organization. It creates a framework for decision making, accountability, and oversight across the data lifecycle, from creation and storage to sharing and disposal.</p>

  <p>Data governance initiatives improve the quality of data by assigning a team responsible for data’s accuracy, completeness, consistency, timeliness, validity, and uniqueness.</p>

  <p>Leaders of successful data governance programs declared at the Data Governance Conference […] that data governance is about 80 to 95 percent communication.</p>
</blockquote>

<p>As you can see, all these points apply to humans: processes, teams, communication… There is nothing technical in data fundamentals. Sure, technology should help us avoid bad data quality, but technology on its own cannot be at the core.</p>

<p>I am totally aware that this solution is a tall ask. Companies are like oil tankers, which require miles to change course or stop. Nobody likes to be told that they have to work differently, and there is a natural resistance. Unfortunately, I cannot think of any other option. On the other hand, I have worked with many companies that have taken good care of their data governance and they are happily using their data to achieve their goals.</p>

<p> </p>

<p><small><a href="https://www.pexels.com/photo/people-working-together-by-table-at-office-7691724/">Photo by Yan Krukau</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="Opinion" /><summary type="html"><![CDATA[I have spent the last five years of my career at Adobe, working with the Adobe Experience Platform (AEP). I cannot say “I have seen it all” (I wish I could), but I have seen many different implementations. It is thanks to this experience that I can share with you the content of this blog. While the majority of my customers have had great data to start with, from time to time I have heard the concern that their data is of bad quality and they need to clean it up before sending it to AEP.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-06-07-bad-data-quality-bad-data-governance/data-review.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-06-07-bad-data-quality-bad-data-governance/data-review.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Adobe Target and Adobe Journey Optimizer Coexistence - III</title><link href="https://www.pedromonjo.com/2026/05/target-ajo-coexistence-iii.html" rel="alternate" type="text/html" title="Adobe Target and Adobe Journey Optimizer Coexistence - III" /><published>2026-05-17T00:00:00-04:00</published><updated>2026-05-17T00:00:00-04:00</updated><id>https://www.pedromonjo.com/2026/05/target-ajo-coexistence-iii</id><content type="html" xml:base="https://www.pedromonjo.com/2026/05/target-ajo-coexistence-iii.html"><![CDATA[<p>A few days ago I reviewed the two blog posts I wrote on Adobe Target and Adobe Journey Optimizer Coexistence (<a href="https://www.pedromonjo.com/2026/03/target-ajo-coexistence-i.html">part one</a> and <a href="https://www.pedromonjo.com/2026/04/target-ajo-coexistence-ii.html">part two</a>). While I wrote them, I did my best to make them understandable, but I have realized that I made them difficult to follow. My premise has always been not to delete old posts, even if they are no longer useful, like the series on <a href="https://www.pedromonjo.com/category/dtm/">DTM</a>. So, instead, here you have a table, which I hope will be much more digestible.</p>

<!--more-->

<p>If you have not read those posts or do not remember them (I will not blame you), the problem to solve is the situation where you have Adobe Journey Optimizer (AJO) offers and Adobe Target (AT) on the same web page. We want to make sure that one does not interfere with the other. There are various approaches, which I summarize in the tables below. I have added some more cases that I did not consider in my previous posts.</p>

<p>Some clarifications:</p>

<ul>
  <li>Private section refers to the section of the website only for logged-in visitors.</li>
  <li>Public section is the part of the website visible to everybody.</li>
  <li>AT Premium applies to all cases, except in option A, where it only applies to the public section.</li>
</ul>

<table id="table1" class="table table-bordered">
  <thead>
    <tr>
      <th>Option</th>
      <th>AJO Offers</th>
      <th>AT XT</th>
      <th>AT A/B</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>A</td>
      <td>Private section</td>
      <td>Public section</td>
      <td>Public section</td>
    </tr>
    <tr>
      <td>B</td>
      <td>Private section</td>
      <td>Public section</td>
      <td>Everywhere</td>
    </tr>
    <tr>
      <td>C</td>
      <td>Known visitors</td>
      <td>Anonymous</td>
      <td>Everybody</td>
    </tr>
    <tr>
      <td>D</td>
      <td>Arbitration</td>
      <td>Arbitration</td>
      <td>Everywhere or Arbitration</td>
    </tr>
  </tbody>
</table>

<p>A different view of the same information:</p>

<table id="table2" class="table table-bordered">
  <thead>
    <tr>
      <th>Option</th>
      <th>Public section</th>
      <th>Private section</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>A</td>
      <td>Everybody: AT XT + AT A/B</td>
      <td>Known visitors: AJO Offers</td>
    </tr>
    <tr>
      <td>B</td>
      <td>Everybody: AT XT + AT A/B</td>
      <td>Known visitors: AJO Offers + AT A/B</td>
    </tr>
    <tr>
      <td>C</td>
      <td>Known visitors: AJO Offers + AT A/B<br />Anonymous visitors: AT XT + AT A/B</td>
      <td>Known visitors: AJO Offers + AT A/B</td>
    </tr>
    <tr>
      <td>D</td>
      <td>Arbitration</td>
      <td>Arbitration</td>
    </tr>
  </tbody>
</table>

<style type="text/css">
table#table1 th:first-of-type {
    width: 10%;
}
table#table1 th:nth-child(1), table#table1 td:nth-child(1) {
  width: 10%;
}
table#table1 th:nth-child(n+2),table#table1 td:nth-child(n+2) {
  width: 30%;
}
table#table2 th:first-of-type {
    width: 10%;
}
table#table2 th:nth-child(1), table#table2 td:nth-child(1) {
  width: 10%;
}
table#table2 th:nth-child(n+2),table#table2 td:nth-child(n+2) {
  width: 45%;
}
</style>

<p>I hope this is now clearer. I know it is not easy to envision all options. Let me know if you have seen another configuration that has worked without major issues.</p>

<p> </p>

<p><small>Photo by <a href="https://unsplash.com/@peterbbryan?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Peter Bryan</a> on <a href="https://unsplash.com/photos/a-close-up-of-a-clock-with-gears-attached-to-it-KORGgJI1odA?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="MSA" /><category term="Platform" /><summary type="html"><![CDATA[A few days ago I reviewed the two blog posts I wrote on Adobe Target and Adobe Journey Optimizer Coexistence (part one and part two). While I wrote them, I did my best to make them understandable, but I have realized that I made them difficult to follow. My premise has always been not to delete old posts, even if they are no longer useful, like the series on DTM. So, instead, here you have a table, which I hope will be much more digestible.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-05-17-target-ajo-coexistence-iii/clockwork.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-05-17-target-ajo-coexistence-iii/clockwork.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Definition of Conversion</title><link href="https://www.pedromonjo.com/2026/05/definition-conversion.html" rel="alternate" type="text/html" title="Definition of Conversion" /><published>2026-05-03T00:00:00-04:00</published><updated>2026-05-03T00:00:00-04:00</updated><id>https://www.pedromonjo.com/2026/05/definition-conversion</id><content type="html" xml:base="https://www.pedromonjo.com/2026/05/definition-conversion.html"><![CDATA[<p>The history of marketing is the history of trying to answer one fundamental question: did this effort increase revenue? As long as products were sold offline and marketing was broad and generic, this question was answered statistically and roughly. With digital marketing, it became possible to connect a specific campaign to a specific purchase, and over time more and more intermediate events were tracked. That richer picture is useful, but it also adds a surprising amount of complexity.</p>

<!--more-->

<p>This complexity shows up in a very fundamental place: how we define a “conversion” in the first place.</p>

<h2 id="what-is-a-conversion">What is a conversion?</h2>

<p>In some industries, like retail or travel and hospitality, a conversion seems straightforward: a customer buys something, and that transaction is the success metric. However, once you step outside these simple scenarios, the answer is no longer obvious.</p>

<p>Consider a few examples:</p>

<ul>
  <li><em>Financial institutions</em>. It is clear that they aim to make money and that digital marketing matters as much as in any other vertical. But what exactly is a conversion for a bank? Is it opening an account, even though that is initially a cost for the bank? Is it the first deposit? Is it when the account reaches a certain balance?</li>
  <li><em>Telecommunications</em>. When you get a new line, you usually pay only the first month up front. Subsequent monthly payments are not recorded at the moment of opening the line. Should all future payments be attributed to the original campaign that brought the customer to the carrier? What about a subsidized phone, which initially costs the carrier more than the sale price?</li>
  <li><em>Consumer packaged goods (CPG)</em>. Here the situation is even more complex. Most CPG companies do not sell directly to customers; they sell through supermarkets and other distributors. These intermediaries rarely share detailed sales data with their suppliers, making it very hard to tie a specific campaign to a specific sale.</li>
</ul>

<p>These examples show that even something as basic as “what counts as success” is not universal, and that has big consequences for how we measure performance.</p>

<h2 id="micro-vs-macro-conversion">Micro vs macro-conversion</h2>

<p>To manage this complexity, digital marketers started to distinguish between different types of conversions. As digital marketing became more sophisticated, teams began to track not only purchases but also intermediate steps that signal progress toward a purchase. A new qualifier appeared: not all conversions are equal.</p>

<ul>
  <li><em>Micro-conversions</em> are meaningful steps a customer takes on the way to a final conversion. Examples include creating a web account, clicking on an internal campaign link, or using the chat functionality.</li>
  <li><em>Macro-conversions</em> are the final events that usually represent direct business value, such as a transaction or a contract signed.</li>
</ul>

<p>This distinction is useful, but it also makes it even more important to be explicit about what your conversions are.</p>

<h2 id="define-your-conversions">Define your conversions</h2>

<p>All of this complexity has one practical implication: you cannot assume that everyone in your company shares the same definition of a conversion. We tend to take for granted that we all mean the same thing, but you might be surprised once you start asking a few uncomfortable questions.</p>

<p>Here is some guidance to define your conversions. Notice the plural: you will probably need more than one definition, depending on the use case.</p>

<ul>
  <li>When running A/B tests, be clear on what you are trying to optimize. Some will argue it should be revenue; others will prefer clicks on the banner, especially when what happens later may not be directly connected to that banner.</li>
  <li>List and prioritize the micro-conversions that signal a customer’s intent to eventually convert. Not all micro-conversions have the same weight, and some may be much better predictors of future value.</li>
  <li>For subscription services, decide whether the conversion is the first month’s fee, the first year’s revenue, or an average revenue per customer over time. Different choices will change how you evaluate campaigns and which ones you consider successful.</li>
  <li>When conversions do not have an obvious monetary value, consider assigning one. This applies to both macro-conversions and, especially, micro-conversions. You may be able to use statistical analysis to estimate how a given conversion contributes to total revenue and turn that into an approximate currency value.</li>
</ul>

<p>The point is not to discover a single “correct” definition of conversion, but to choose clear, consistent definitions that match your business model and share them across your organization. My suggestion is that any marketing campaign should not only describe the activities, but clearly define the converstion.</p>

<p> </p>

<p><small>Photo by <a href="https://unsplash.com/@claybanks?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Clay Banks</a> on <a href="https://unsplash.com/photos/person-holding-white-pos-machine-E2HgkL3LaFE?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="Opinion" /><summary type="html"><![CDATA[The history of marketing is the history of trying to answer one fundamental question: did this effort increase revenue? As long as products were sold offline and marketing was broad and generic, this question was answered statistically and roughly. With digital marketing, it became possible to connect a specific campaign to a specific purchase, and over time more and more intermediate events were tracked. That richer picture is useful, but it also adds a surprising amount of complexity.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-05-03-definition-conversion/purchase.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-05-03-definition-conversion/purchase.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Customer Experience Orchestration</title><link href="https://www.pedromonjo.com/2026/04/customer-experience-orchestration.html" rel="alternate" type="text/html" title="Customer Experience Orchestration" /><published>2026-04-19T00:00:00-04:00</published><updated>2026-04-19T00:00:00-04:00</updated><id>https://www.pedromonjo.com/2026/04/customer-experience-orchestration</id><content type="html" xml:base="https://www.pedromonjo.com/2026/04/customer-experience-orchestration.html"><![CDATA[<p>If you are going to Summit (unfortunately, this year I am not going) you will hear a lot about Customer Experience Orchestration (CXO). This term is not new, you can find a <a href="https://business.adobe.com/summit/2025/sessions/customer-experience-orchestration-gs1-2.html">presentation from Summit 2025 where Anil talks about it</a>. However, I wanted to talk more about it, as it is another mindshift that we have to take.</p>

<!--more-->

<h2 id="a-bit-of-history">A Bit of History</h2>

<p>I am fascinated by planes. I follow a few YouTube channels about them. To me, it is almost unbelievable that we went from</p>

<p><a data-toggle="lightbox" href="https://www.pedromonjo.com/assets/images/originals/2026/2026-04-19-customer-experience-orchestration/wright-brothers.jpg"><img loading="lazy" src="https://www.pedromonjo.com/assets/images/responsive/2026/2026-04-19-customer-experience-orchestration/wright-brothers-300-e0afaeff2.jpg" alt="Wright Brothers" srcset="https://www.pedromonjo.com/assets/images/responsive/2026/2026-04-19-customer-experience-orchestration/wright-brothers-300-e0afaeff2.jpg 1.0x, https://www.pedromonjo.com/assets/images/responsive/2026/2026-04-19-customer-experience-orchestration/wright-brothers-450.0-e0afaeff2.jpg 1.5x, https://www.pedromonjo.com/assets/images/responsive/2026/2026-04-19-customer-experience-orchestration/wright-brothers-600-e0afaeff2.jpg 2.0x"></a></p>

<p>to</p>

<p><a data-toggle="lightbox" href="https://www.pedromonjo.com/assets/images/originals/2026/2026-04-19-customer-experience-orchestration/concorde.jpg"><img loading="lazy" src="https://www.pedromonjo.com/assets/images/responsive/2026/2026-04-19-customer-experience-orchestration/concorde-300-d026f85f1.jpg" alt="Concorde" srcset="https://www.pedromonjo.com/assets/images/responsive/2026/2026-04-19-customer-experience-orchestration/concorde-300-d026f85f1.jpg 1.0x, https://www.pedromonjo.com/assets/images/responsive/2026/2026-04-19-customer-experience-orchestration/concorde-450.0-d026f85f1.jpg 1.5x, https://www.pedromonjo.com/assets/images/responsive/2026/2026-04-19-customer-experience-orchestration/concorde-600-d026f85f1.jpg 2.0x"></a></p>

<p>in just over 60 years.</p>

<p>If you have been in the Digital Marketing space for a decade or more, you will have noticed a similar evolution from the early days to today. However, instead of taking 60 years, it has only taken 20.</p>

<p>When Adobe acquired Omniture in 2009, very few understood the move. In the years since then there have been more acquisitions in the Digital Experience front: Day Software, Demdex, Neolane, Efficient Frontier, Workfront… Then, these purchases started to make more sense.</p>

<p>However, bringing all these different tools together and connecting them to the Adobe Experience Platform has not been easy: different technologies, different approaches, different perspectives had to be aligned. The very reason why the <a href="https://www.pedromonjo.com/category/msa/">Multi-Solution Architect</a> role was created was to bring all tools together.</p>

<p>This evolution has also been seen on the business side, although slower. Initially, people were only interested in their own tool. I remember with nostalgia the days when I was consulting on Adobe <del>SiteCatalyst</del> Analytics and Adobe Audience Manager. My customers were only interested in their own tool. Nobody working with Adobe Analytics cared about Adobe Campaign, and vice-versa, except perhaps for the Genesys integration.</p>

<p>Over the years, though, this siloed approach started to evolve into families: data, experience, and orchestration. People needed to use various tools to generate better marketing. For example, it was not enough for content creators to master Adobe Experience Manager, they also had to work with Adobe Target through the Experience Fragments integration. Experiences were served from both tools and they had to manage all experiences.</p>

<h2 id="customer-experience-orchestration">Customer Experience Orchestration</h2>

<p>The last jump in this evolution has been to understand that all these tools work together to achieve business goals, and those business goals do not care about tools. Consequently, marketers need to be aware of all the technologies that are involved.</p>

<p>Let’s consider an example: sending an email. Something as simple as that requires, at least:</p>

<ul>
  <li>Target audience</li>
  <li>Design</li>
  <li>Text</li>
  <li>Images</li>
  <li>Constraints (e.g. timezone, fatigue)</li>
  <li>Metrics</li>
  <li>Reports</li>
</ul>

<p>Any other digital marketing example will have a similar set of components, which have to be done by people, using multiple tools. No longer can you just limit yourself to a single tool.</p>

<p>In other words, this is a factory, not a workshop, and a factory needs to run like clockwork. It is the same truth that Henry Ford realized more than a century ago. Everybody has a position, requires inputs from others, and their outputs subsequently become the inputs of the next stages. One direct consequence is that you have to understand the full ecosystem, not just your niche. This applies to both technical and business roles.</p>

<p>CXO is basically the consolidation of what I have just explained. It is just that we are giving it a name and formalizing it. Getting into the details, CXO is composed of:</p>

<ul>
  <li>Customer Engagement: involves gathering all the data needed to personalize the customer experience.</li>
  <li>Content Supply Chain: guarantees that content goes from ideation to realization.</li>
  <li>Brand Visibility: ensures that your brand stays relevant in this changing world.</li>
</ul>

<p>Moving forward, not only do we all need to know what our role in this factory is, but also what everybody else is doing in it. This is not a theory, my most advanced customers have already started this journey. They have structured their MarTech team with CXO in mind, even if they do not use this term.</p>

<p> </p>

<p><small><a href="https://www.pexels.com/photo/a-salesman-giving-the-paper-bag-to-the-person-6127382/">Photo by cottonbro studio</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="Opinion" /><summary type="html"><![CDATA[If you are going to Summit (unfortunately, this year I am not going) you will hear a lot about Customer Experience Orchestration (CXO). This term is not new, you can find a presentation from Summit 2025 where Anil talks about it. However, I wanted to talk more about it, as it is another mindshift that we have to take.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-04-19-customer-experience-orchestration/customer-experience.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-04-19-customer-experience-orchestration/customer-experience.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Adobe Target and Adobe Journey Optimizer Coexistence - II</title><link href="https://www.pedromonjo.com/2026/04/target-ajo-coexistence-ii.html" rel="alternate" type="text/html" title="Adobe Target and Adobe Journey Optimizer Coexistence - II" /><published>2026-04-05T00:00:00-04:00</published><updated>2026-04-05T00:00:00-04:00</updated><id>https://www.pedromonjo.com/2026/04/target-ajo-coexistence-ii</id><content type="html" xml:base="https://www.pedromonjo.com/2026/04/target-ajo-coexistence-ii.html"><![CDATA[<p>This post is a continuation of the <a href="https://www.pedromonjo.com/2026/03/target-ajo-coexistence-i.html">first part of the Adobe Target (AT) and Adobe Journey Optimizer (AJO) coexistence</a>. If you have not read it, you will not understand this post. I strongly suggest that you start there.</p>

<!--more-->

<p>Let’s continue with the options to allow AT and AJO offers to coexist.</p>

<h2 id="only-use-ajo-for-personalization">Only Use AJO for Personalization</h2>

<p>If you are running offers on both AT and AJO, why are you managing two systems? Would it not make more sense to use the best tool for personalization?</p>

<p>AJO offers, especially if you choose to go with the latest version, AJO Decisioning, is the most advanced solution to manage offers. You can add start and end dates, priorities, capping rules, qualification rules, and you can use propensity scores in ranking formulas. While you may be able to do the same in AT, you would need to use <a href="https://experienceleague.adobe.com/en/docs/target/using/audiences/visitor-profiles/profile-parameters">profile scripts</a>, with its limitations, or complex AT activities.</p>

<p>With this approach, you would use AJO for personalization activities, and AT for A/B tests and all premium features. Obviously, you would not be able to run an A/B test and an AJO personalization activity on the same placement. However, I would argue that, if you want to do that, you have not thought it through well enough.</p>

<p>If this were the absolutely best solution, everybody would be using it, but the reality is that this is not the case. The reason is very simple: AT has the <a href="https://experienceleague.adobe.com/en/docs/target/using/experiences/vec/visual-experience-composer">Visual Experience Composer</a>, which makes it much more marketer friendly than AJO. In AJO, you need to generate or manipulate the HTML manually. This is the main drawback of this approach. That being said, I have had customers who had an IT resource dedicated to the marketing team, to manage all technical work like this.</p>

<p>There is another drawback: reporting. With <a href="https://www.pedromonjo.com/2016/08/analytics-for-target-a4t.html">A4T</a>, you have all the reporting done automatically, whereas if you use AJO, you will need to prepare the reporting more manually.</p>

<p>The final major issue is that AJO offers were designed for known visitors. Personalization for anonymous visitors will be challenging, if not impossible.</p>

<h2 id="the-arbitrator">The Arbitrator</h2>

<p>The final and most complex solution is to create an arbitration layer between the browser, AJO, and AT. I can think of two options to develop this arbitration layer:</p>

<ul>
  <li><em>Microservice</em>. You could set up a microservice, which would be invoked from the browser. This microservice would then request the AJO offers and AT activities, would evaluate which content should be returned to the browser, and send it back to the browser. The browser would blindly show whatever the microservice sends back.</li>
  <li><em>JavaScript function</em>. Very similarly to the microservice, this function would request the AJO offers and AT activities, decide which one should be shown, and discard the other one. This code would live in the user’s browser.</li>
</ul>

<p>Each has its pros and cons, and I do not think there is a clear winner. In both cases, you need to write custom code, which is why I mention this is the most complex solution. The microservice requires extra infrastructure, but it will be faster than doing everything in the browser.</p>

<p>There is one detail that is very important to keep in mind with this approach: the difference between decision and impression. Let me explain.</p>

<p>By default, AT increments the impression counter every time a decision is made to send an experience back to the browser. In other words, AT assumes that all retrieved experiences are shown. Remember that AT and A4T reports use this impression metric heavily. With an arbitrator, not all retrieved experiences will be shown. This poses a problem for AT: the reports will be completely wrong. Fortunately, AT has a solution for this edge case. Since I recently wrote a full blog post on it, I will not cover the solution here and just reference my other post: <a href="https://www.pedromonjo.com/2026/03/conditional-adobe-target-rendering.html">Conditional Adobe Target Rendering</a>.</p>

<p>The case of AJO offers is different. AJO will always send a record to a system dataset for every decision made. If you want to <a href="https://experienceleague.adobe.com/en/docs/journey-optimizer-learn/frequency-capping-in-ajod/capture-impression-click-events">capture the impressions or the clicks</a>, which are not mandatory, you should send an event back to AEP using a standard <code class="language-plaintext highlighter-rouge">eventType</code>: <code class="language-plaintext highlighter-rouge">decisioning.propositionDisplay</code> for impressions or <code class="language-plaintext highlighter-rouge">decisioning.propositionInteract</code> for clicks.</p>

<p>I know I have skipped many steps regarding this arbitrator. Since it is custom code, the business rules will be different per company, and it is a complex piece of software, I cannot offer a generic solution or sample code. YMMV.</p>

<p> </p>

<p><small>Photo by <a href="https://unsplash.com/@familyschaffner?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Astrid Schaffner</a> on <a href="https://unsplash.com/photos/a-pole-with-a-bunch-of-signs-on-it-qcIkeYYALT4?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="MSA" /><category term="Platform" /><summary type="html"><![CDATA[This post is a continuation of the first part of the Adobe Target (AT) and Adobe Journey Optimizer (AJO) coexistence. If you have not read it, you will not understand this post. I strongly suggest that you start there.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-04-05-target-ajo-coexistence-ii/decisions.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-04-05-target-ajo-coexistence-ii/decisions.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Adobe Target and Adobe Journey Optimizer Coexistence - I</title><link href="https://www.pedromonjo.com/2026/03/target-ajo-coexistence-i.html" rel="alternate" type="text/html" title="Adobe Target and Adobe Journey Optimizer Coexistence - I" /><published>2026-03-22T00:00:00-04:00</published><updated>2026-03-22T00:00:00-04:00</updated><id>https://www.pedromonjo.com/2026/03/target-ajo-coexistence-i</id><content type="html" xml:base="https://www.pedromonjo.com/2026/03/target-ajo-coexistence-i.html"><![CDATA[<p>In the last few months, I have been working with a customer on a topic that is becoming more and more common. No one questions the power of personalization, but now we have a conflict to resolve: what happens when you combine Adobe Target (AT) and Adobe Journey Optimizer (AJO) offers on the same page?</p>

<!--more-->

<h2 id="a-bit-of-background">A Bit of Background</h2>

<p>There is little to no overlap in the scope of most Adobe Experience Cloud tools:</p>

<ul>
  <li>Although the names are similar, Adobe Experience Platform (AEP) and Adobe Experience Manager (AEM) are worlds apart.</li>
  <li>You can create a website with Adobe Commerce, but if you have AEM, you will likely use the latter.</li>
  <li>AT has its own reporting capability, but if you have Adobe Analytics or Customer Journey Analytics, we prefer to use one of these analytics tools for A4T reporting.</li>
</ul>

<p>However, with the release of AJO offers, its web capabilities compete directly with AT.</p>

<p>I will be referring to AJO offers generically. For completeness, AJO has two engines, the legacy Offer Decisioning Engine, now renamed as <a href="https://experienceleague.adobe.com/en/docs/journey-optimizer/using/decisioning/offer-decisioning/offer-decisioning-landing-page">Decision Management</a>, and the new Experience Decisioning, also known as <a href="https://experienceleague.adobe.com/en/docs/journey-optimizer/using/decisioning/experience-decisioning/experience-decisioning-landing-page">Decisioning</a>. I know, Adobe could have done a better job in naming these two features. I will not differentiate between the two in this post, as the differences do not change the explanations. I also assume that only one of these two AJO offer engines is in use. I strongly recommend against using both simultaneously; here be dragons.</p>

<p>Finally, if you want a different perspective from mine, my coworker Sarvesh has a <a href="https://www.linkedin.com/pulse/navigating-complexities-running-adobe-target-ajo-d-offer-marathe-mv69c/">post on LinkedIn</a> on the same topic.</p>

<h2 id="the-problem">The Problem</h2>

<p>While AT was designed for the web, and it can also be used in mobile apps, AJO offers can be used in any channel that can make API calls. I know, I know, you could, in theory, use AT <em>rawboxes</em> for email open-time personalization, but who uses them these days?</p>

<p>If you have been following so far, you will have noticed that on the web, we can potentially have AT experiences and AJO offers simultaneously. <em>Per se</em>, this is not a problem. However, nothing prevents you from configuring an AT experience and an AJO offer to be shown on exactly the same placement. AJO and AT have no way to communicate in the backend to detect a collision. I do not expect this capability to be built, so we have a conflict to resolve. I would even argue that it is impossible. As a consequence, we have to resolve this collision externally.</p>

<p>Let’s explore what solutions we have at hand.</p>

<h2 id="mutual-exclusivity">Mutual Exclusivity</h2>

<p>The simplest solution is also the most obvious: use one or the other on a given page, never both at the same time. The most common approach is to use AT on publicly-facing pages, and AJO offers on the private section of the website.</p>

<p>On the one hand, the public section of a website is open to everyone. Most visitors will be anonymous, so you will not have a profile for them that you can use to show them an offer. AT is at its best in this scenario, from A/B testing to Recommendations.</p>

<p>On the other hand, the private section requires authentication. You can now access a profile and use AJO offers to find the best content for the visitor.</p>

<p>The benefits of this approach are obvious:</p>

<ul>
  <li>Simple governance.</li>
  <li>Separation of concerns.</li>
  <li>Predictability.</li>
  <li>Attribution in AT will be more accurate.</li>
</ul>

<h2 id="activity-type-separation">Activity Type Separation</h2>

<p>One drawback of the previous solution is that you cannot run A/B tests on the private section of your website. In order to enable this capability, you could take one of the following two approaches.</p>

<p>On the public section of the website, you continue using AT only. Nothing changes here. However, on the private section, you allow AT for A/B testing, and only A/B testing. AJO offers still show offers in the private section. It goes without saying that A/B tests should not be performed in locations where AJO offers are shown.</p>

<p>A slightly different approach is to use AT for A/B testing and AJO offers for personalization everywhere on the website. Again, you will have to define where you can run personalization campaigns and where to run A/B tests, and keep these areas mutually exclusive.</p>

<p>The main advantage of this solution is that you can now run A/B tests everywhere on the web. However, you have to define the boundaries between AT and AJO offers carefully.</p>

<p>Since this post is already quite long, I will continue in the next post.</p>

<p> </p>

<p><small>Photo by <a href="https://unsplash.com/@soberanes?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Uriel Soberanes</a> on <a href="https://unsplash.com/photos/two-bisons-fighting-head-L1bAGEWYCtk?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="MSA" /><category term="Platform" /><summary type="html"><![CDATA[In the last few months, I have been working with a customer on a topic that is becoming more and more common. No one questions the power of personalization, but now we have a conflict to resolve: what happens when you combine Adobe Target (AT) and Adobe Journey Optimizer (AJO) offers on the same page?]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-03-22-target-ajo-coexistence-i/collision.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-03-22-target-ajo-coexistence-i/collision.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Conditional Adobe Target Rendering</title><link href="https://www.pedromonjo.com/2026/03/conditional-adobe-target-rendering.html" rel="alternate" type="text/html" title="Conditional Adobe Target Rendering" /><published>2026-03-01T00:00:00-05:00</published><updated>2026-03-01T00:00:00-05:00</updated><id>https://www.pedromonjo.com/2026/03/conditional-adobe-target-rendering</id><content type="html" xml:base="https://www.pedromonjo.com/2026/03/conditional-adobe-target-rendering.html"><![CDATA[<p>When using Adobe Target, in the most general case, you always want to show the new experience: the page loads with the default content, Web SDK requests alternate content from Adobe Target, and this new content replaces the default content. This is how most of our customers use Adobe Target. However, there are legitimate reasons for not always wanting to show the response from Adobe Target. I will not explain here the use cases when this behavior makes sense, but rather how you need to set up Web SDK so that you can control the rendering.</p>

<!--more-->

<h2 id="proposition-impressions">Proposition Impressions</h2>

<p>You may think that the easy solution is just to prevent the Web SDK code, which renders the content in Adobe Target’s response, from running. However, it is not that easy, as I will explain below. Before I continue, I want to clarify that this response is usually called a proposition. The documentation uses this word, and so will I throughout this post.</p>

<p>Adobe Target has no way of knowing what has happened in the browser. It therefore considers the returned experience rendered and increases the impression counter for that experience. This counter, in turn, is used to calculate lift and confidence, the two main metrics of Adobe Target. In other words, if we do nothing, in cases where a proposition is not shown and the impression counter is increased, the reports will be incorrect.</p>

<p>Fortunately, there is a solution. Adobe Target can be told <strong>not</strong> to increment the impression counter automatically. Instead, a different process needs to be followed:</p>

<ol>
  <li>Request the proposition from Adobe Target, without incrementing the counter.</li>
  <li>Decide whether to render the proposition.</li>
  <li>If the proposition is rendered, send a view event back to Adobe Target.</li>
</ol>

<h2 id="web-sdk">Web SDK</h2>

<p>If you are using Adobe <del>Launch</del> Tags, you have to configure the Adobe Target send event action at the top of the page with these checkboxes.</p>

<ul>
  <li>Disable “guided events”. By default, Web SDK will try to make your life simpler by only allowing you to configure the typical options. We want to go our own way.
<a data-toggle="lightbox" href="https://www.pedromonjo.com/assets/images/originals/2026/2026-03-01-conditional-adobe-target-rendering/guided-events.png"><img loading="lazy" src="https://www.pedromonjo.com/assets/images/responsive/2026/2026-03-01-conditional-adobe-target-rendering/guided-events-300-3fb72bb9f.png" alt="Guided Events" srcset="https://www.pedromonjo.com/assets/images/responsive/2026/2026-03-01-conditional-adobe-target-rendering/guided-events-300-3fb72bb9f.png 1.0x, https://www.pedromonjo.com/assets/images/responsive/2026/2026-03-01-conditional-adobe-target-rendering/guided-events-450.0-3fb72bb9f.png 1.5x, https://www.pedromonjo.com/assets/images/responsive/2026/2026-03-01-conditional-adobe-target-rendering/guided-events-600-3fb72bb9f.png 2.0x"></a></li>
  <li>Disable “render visual personalization decisions”. You can only change this checkbox after disabling guided events.
<a data-toggle="lightbox" href="https://www.pedromonjo.com/assets/images/originals/2026/2026-03-01-conditional-adobe-target-rendering/render-visual-personalization-decisions.png"><img loading="lazy" src="https://www.pedromonjo.com/assets/images/responsive/2026/2026-03-01-conditional-adobe-target-rendering/render-visual-personalization-decisions-300-62cb73be3.png" alt="Render visual personalization decisions" srcset="https://www.pedromonjo.com/assets/images/responsive/2026/2026-03-01-conditional-adobe-target-rendering/render-visual-personalization-decisions-300-62cb73be3.png 1.0x, https://www.pedromonjo.com/assets/images/responsive/2026/2026-03-01-conditional-adobe-target-rendering/render-visual-personalization-decisions-450.0-62cb73be3.png 1.5x, https://www.pedromonjo.com/assets/images/responsive/2026/2026-03-01-conditional-adobe-target-rendering/render-visual-personalization-decisions-600-62cb73be3.png 2.0x"></a></li>
</ul>

<p>With these two parameters, you will get the propositions, but nothing else will happen: Adobe Target will not increase the counters, and Web SDK will not show the proposition. Now you have to decide whether you should render the propositions and, if so, render them and notify Adobe Target.</p>

<p>It is beyond the scope of this post to go into the details of how to decide and render the propositions. This will entirely depend on the business requirements for the decision and the structure of your website’s HTML. What I can do is guide you on how to implement it.</p>

<p>The Platform Web SDK tag extension provides a “Send Event Complete” event, which can be used to trigger a new rule when a response from a Send Event action is received. I can think of two options for how to use it:</p>

<ul>
  <li>If the decision to render can easily be constructed in a rule condition and the rendering is simple:
    <ul>
      <li>Add the condition to the rule</li>
      <li>Add an action of type “Apply propositions” and configure it to render the proposition.
<a data-toggle="lightbox" href="https://www.pedromonjo.com/assets/images/originals/2026/2026-03-01-conditional-adobe-target-rendering/apply-proposition.png"><img loading="lazy" src="https://www.pedromonjo.com/assets/images/responsive/2026/2026-03-01-conditional-adobe-target-rendering/apply-proposition-300-82b71827d.png" alt="Apply Proposition" srcset="https://www.pedromonjo.com/assets/images/responsive/2026/2026-03-01-conditional-adobe-target-rendering/apply-proposition-300-82b71827d.png 1.0x, https://www.pedromonjo.com/assets/images/responsive/2026/2026-03-01-conditional-adobe-target-rendering/apply-proposition-450.0-82b71827d.png 1.5x, https://www.pedromonjo.com/assets/images/responsive/2026/2026-03-01-conditional-adobe-target-rendering/apply-proposition-600-82b71827d.png 2.0x"></a></li>
    </ul>
  </li>
  <li>If the decision and rendering require custom code, add a “Custom code” action, where you will add the necessary code to evaluate the decision to show the proposition and handle its rendering. In your code, you will want to use <code class="language-plaintext highlighter-rouge">event.propositions</code> to get the response from Adobe Target.</li>
</ul>

<p>Refer to this <a href="https://experienceleague.adobe.com/en/docs/platform-learn/implement-web-sdk/applications-setup/setup-target#process-the-response-from-target">help page</a> for more information.</p>

<p>Finally, it is time to report back to Adobe Target whether the proposition has been rendered, which includes activities with <a href="https://www.pedromonjo.com/2016/08/analytics-for-target-a4t.html">Analytics for Target (A4T)</a>. This can easily be done using a normal send event action in Web SDK. Since this event is only for sending data, you can try to use the “collect” method:
<a data-toggle="lightbox" href="https://www.pedromonjo.com/assets/images/originals/2026/2026-03-01-conditional-adobe-target-rendering/proposition-display.png"><img loading="lazy" src="https://www.pedromonjo.com/assets/images/responsive/2026/2026-03-01-conditional-adobe-target-rendering/proposition-display-300-11d59475a.png" alt="Proposition Display" srcset="https://www.pedromonjo.com/assets/images/responsive/2026/2026-03-01-conditional-adobe-target-rendering/proposition-display-300-11d59475a.png 1.0x, https://www.pedromonjo.com/assets/images/responsive/2026/2026-03-01-conditional-adobe-target-rendering/proposition-display-450.0-11d59475a.png 1.5x, https://www.pedromonjo.com/assets/images/responsive/2026/2026-03-01-conditional-adobe-target-rendering/proposition-display-600-11d59475a.png 2.0x"></a></p>

<p>In the previous screenshot, the XDM data is missing. It must contain the proposition information (the Adobe Target experience details) following this structure:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"_experience"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"decisioning"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"propositions"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
        </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="err">&lt;PROPOSITION_ID&gt;</span><span class="p">,</span><span class="w">
        </span><span class="nl">"scope"</span><span class="p">:</span><span class="w"> </span><span class="err">&lt;PROPOSITION_SCOPE&gt;</span><span class="p">,</span><span class="w">
        </span><span class="nl">"scopeDetails"</span><span class="p">:</span><span class="w"> </span><span class="err">&lt;PROPOSITION_SCOPE_DETAILS&gt;</span><span class="w">
      </span><span class="p">}]</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The values for <code class="language-plaintext highlighter-rouge">propositions[].id</code>, <code class="language-plaintext highlighter-rouge">propositions[].scope</code>, and <code class="language-plaintext highlighter-rouge">propositions[].scopeDetails</code> are just a copy of the values that came from the Adobe Target response. You will have noticed that <code class="language-plaintext highlighter-rouge">propositions</code> is an array, in case you have multiple propositions to report on in a single call. Again, refer to this <a href="https://experienceleague.adobe.com/en/docs/platform-learn/implement-web-sdk/applications-setup/setup-target#process-the-response-from-target">help page</a> for your reference.</p>

<h2 id="alloyjs">Alloy.js</h2>

<p>If you are not using Adobe Tags and Web SDK, you will have to do everything above manually, in code. The documentation in Experience League explains it fairly well; you just need to find it, as it is a bit scattered. While I cannot provide boilerplate code, I can provide some guidance. Before I continue, I have to warn you that this code is completely untested.</p>

<p>To get the propositions and prevent Web SDK from rendering them, you will set <code class="language-plaintext highlighter-rouge">renderDecisions</code> to <code class="language-plaintext highlighter-rouge">false</code> and customize <code class="language-plaintext highlighter-rouge">decisionScopes</code> to your needs:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">alloy</span><span class="p">(</span><span class="dl">"</span><span class="s2">sendEvent</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span>
  <span class="na">personalization</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">decisionScopes</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">__view__</span><span class="dl">"</span><span class="p">],</span>
    <span class="na">sendDisplayEvent</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
    <span class="na">defaultPersonalizationEnabled</span><span class="p">:</span> <span class="kc">true</span>
  <span class="p">},</span>
  <span class="na">renderDecisions</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
  <span class="na">xdm</span><span class="p">:</span> <span class="p">{</span> <span class="cm">/* Your XDM */</span> <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Again, the decisioning and rendering are out of scope for this post.</p>

<p>The last step is to send the display event back to Adobe Target:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">alloy</span><span class="p">(</span><span class="dl">"</span><span class="s2">sendEvent</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> 
  <span class="na">xdm</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">eventType</span><span class="p">:</span> <span class="dl">"</span><span class="s2">decisioning.propositionDisplay</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">_experience</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">decisioning</span><span class="p">:</span> <span class="p">[{</span>
        <span class="dl">"</span><span class="s2">id</span><span class="dl">"</span><span class="p">:</span> <span class="o">&lt;</span><span class="nx">PROPOSITION_ID</span><span class="o">&gt;</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">scope</span><span class="dl">"</span><span class="p">:</span> <span class="o">&lt;</span><span class="nx">PROPOSITION_SCOPE</span><span class="o">&gt;</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">scopeDetails</span><span class="dl">"</span><span class="p">:</span> <span class="o">&lt;</span><span class="nx">PROPOSITION_SCOPE_DETAILS</span><span class="o">&gt;</span>
      <span class="p">}],</span>
      <span class="na">propositionEventType</span><span class="p">:</span> <span class="p">{</span> 
        <span class="na">display</span><span class="p">:</span> <span class="mi">1</span> 
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p>I have used these references:</p>

<ul>
  <li><a href="https://experienceleague.adobe.com/en/docs/experience-platform/collection/js/commands/sendevent/renderdecisions"><code class="language-plaintext highlighter-rouge">renderDecisions</code></a></li>
  <li><a href="https://experienceleague.adobe.com/en/docs/platform-learn/implement-web-sdk/applications-setup/setup-target#process-the-response-from-target">Process the response from Target</a></li>
  <li><a href="https://experienceleague.adobe.com/en/docs/experience-platform/collection/use-cases/personalization/render-manual-propositions">Manually render propositions</a></li>
  <li><a href="https://experienceleague.adobe.com/en/docs/experience-platform/collection/use-cases/personalization/display-events">Manage display events in the Web SDK</a></li>
</ul>

<h2 id="atjs">at.js</h2>

<p>What??? You are still using <code class="language-plaintext highlighter-rouge">at.js</code>? You should <em>really</em> consider migrating to Web SDK. Well, I should not be judgmental.</p>

<p>I will not explain how you should do it, as my knowledge of <code class="language-plaintext highlighter-rouge">at.js</code> is very rusty by now. What I will share is the following:</p>

<ul>
  <li>The process is the same as above: retrieve, and conditionally render and notify.</li>
  <li>Use the <a href="https://developer.adobe.com/target/implement/delivery-api/">Adobe Target Delivery API</a> for both retrieval and notification.</li>
  <li>For experience retrieval, you use <code class="language-plaintext highlighter-rouge">prefetch</code>.</li>
  <li>For display notification, you use <code class="language-plaintext highlighter-rouge">notifications</code>.</li>
</ul>

<p> </p>

<p><small>Photo by <a href="https://unsplash.com/@austinchan?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Austin Chan</a> on <a href="https://unsplash.com/photos/this-is-the-sign-youve-been-looking-for-neon-signage-ukzHlkoz1IE?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="MSA" /><summary type="html"><![CDATA[When using Adobe Target, in the most general case, you always want to show the new experience: the page loads with the default content, Web SDK requests alternate content from Adobe Target, and this new content replaces the default content. This is how most of our customers use Adobe Target. However, there are legitimate reasons for not always wanting to show the response from Adobe Target. I will not explain here the use cases when this behavior makes sense, but rather how you need to set up Web SDK so that you can control the rendering.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-03-01-conditional-adobe-target-rendering/personalized-content.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-03-01-conditional-adobe-target-rendering/personalized-content.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">AI at Adobe</title><link href="https://www.pedromonjo.com/2026/02/ai-adobe.html" rel="alternate" type="text/html" title="AI at Adobe" /><published>2026-02-15T00:00:00-05:00</published><updated>2026-02-15T00:00:00-05:00</updated><id>https://www.pedromonjo.com/2026/02/ai-adobe</id><content type="html" xml:base="https://www.pedromonjo.com/2026/02/ai-adobe.html"><![CDATA[<p>Adobe has been announcing multiple Artificial Intelligence (AI) capabilities as part of the Adobe Experience Cloud. This is just an evolution of Adobe Sensei, the collection of Machine Learning (ML) features that have been released over the last 10+ years. However, with AI things are getting more complicated and it is not always easy to understand where each feature belongs to. I wanted to summarize and classify the features, both for you and me, as our memory is fragile.</p>

<!--more-->

<p>Before I continue, if you have landed on this page looking for AI tools related to the Adobe Document Cloud or the Adobe Creative Cloud, I am afraid I have very little to say about them. Photoshop is as alien for me as Adobe Experience Platform is for you.</p>

<p>With that being said, let’s get to the heart of the matter. I have classified these AI capabilities in three broad groups: assistants, content optimizers and end user support. In the remainder of this post I will explain each one.</p>

<h2 id="assistants">Assistants</h2>

<p>Adobe uses the icon <svg height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg" focusable="false" aria-hidden="true" role="img" class="  ZuUmpre12 FuUmpre12 _va12 I67logd12"><path d="m6.25,18.99805c-.09961,0-.19922-.01953-.29395-.05957-.27637-.11816-.45605-.38965-.45605-.69043v-3.25h-.75c-2.06738,0-3.75-1.68262-3.75-3.75v-5.5c0-2.06738,1.68262-3.75,3.75-3.75h3.95312c.41406,0,.75.33594.75.75s-.33594.75-.75.75h-3.95312c-1.24023,0-2.25,1.00977-2.25,2.25v5.5c0,1.24023,1.00977,2.25,2.25,2.25h1.5c.41406,0,.75.33594.75.75v2.23633l2.88379-2.77637c.13965-.13477.32617-.20996.52051-.20996h4.8457c1.24023,0,2.25-1.00977,2.25-2.25v-1.27148c0-.41406.33594-.75.75-.75s.75.33594.75.75v1.27148c0,2.06738-1.68262,3.75-3.75,3.75h-4.54297l-3.93652,3.79004c-.14258.1377-.33008.20996-.52051.20996Z" fill="var(--iconPrimary, #222)"></path><path d="m13.27832,9.08301c-.18848,0-.37793-.04883-.54883-.14746-.41602-.23926-.62695-.71484-.52637-1.18359l.45996-2.125-1.45996-1.61035c-.32227-.35547-.37793-.87207-.13867-1.28711.24023-.41504.7207-.62598,1.18359-.52637l2.125.45996,1.61035-1.45996c.35547-.32129.875-.37598,1.28711-.13867.41602.23926.62695.71484.52637,1.18359l-.45996,2.125,1.45996,1.61035c.32227.35547.37793.87207.13867,1.28711-.24023.41602-.7168.62891-1.18359.52637l-2.125-.45996-1.61035,1.45996c-.20801.18848-.47168.28613-.73828.28613Zm2.50391-1.88672l-.00293.00293.00293-.00293Zm-2.62988-3.2627l.75977.83789c.23633.25781.33496.62109.26074.96875v.00098l-.23926,1.10645.83789-.75977c.25879-.23633.62598-.33398.96875-.26074l1.10742.23926-.75977-.83789c-.23633-.25781-.33496-.62109-.26074-.96875l.23926-1.10742-.83789.75977c-.25781.2373-.62305.33691-.96875.26074l-1.10742-.23926Zm-.35156,1.8457l.00293.00293-.00293-.00293Zm-.09473-.35352h0Zm4.49023-1.20801l.00293.00293-.00293-.00293Zm-2.97559-1.41699l-.00293.00293.00293-.00293Z" fill="var(--iconPrimary, #222)"></path><path d="m7.93262,11.50391c-.12891,0-.25781-.0332-.375-.10059-.28223-.16309-.42676-.48926-.3584-.80762l.17773-.82031-.56348-.62109c-.21875-.24121-.25684-.59668-.09375-.87891s.49121-.4209.80762-.3584l.82031.17773.62109-.56348c.24219-.21875.5957-.25684.87891-.09375.28223.16309.42676.48926.3584.80762l-.17773.82031.56348.62109c.21875.24121.25684.59668.09375.87891s-.49219.42383-.80762.3584l-.82031-.17773-.62109.56348c-.1416.12793-.32227.19434-.50391.19434Z" fill="var(--iconPrimary, #222)"></path></svg> to denote an AI assistant. You will see it in multiple places, for example:</p>

<ul>
  <li><a href="https://experienceleague.adobe.com/en/docs/experience-platform/ai-assistant/home">Adobe Experience Platform</a></li>
  <li><a href="https://experienceleague.adobe.com/en/docs/journey-optimizer/using/content-management/ai-assistant/ai-assistant-landing-page">Adobe Journey Optimizer</a></li>
  <li><a href="https://experienceleague.adobe.com/en/docs/analytics-platform/using/cja-overview/cja-b2c-overview/ai-assistant">Customer Journey Analytics</a></li>
  <li><a href="https://experienceleague.adobe.com/en/docs/target/using/introduction/assistant-ai/ai-assistant">Adobe Target</a></li>
</ul>

<p>All these assistants have one thing in common: they are meant to help the user of the corresponding tool. Think of them as a virtual intern that takes your orders (your prompt) and works on them in the background. The initial use case was to help you learn more about the tool. However, they are still evolving and, over time, they will do more and more.</p>

<p>Disclaimer: what I am going to explain is not <em>currently</em> available; I am sure it will be in the future. In my wildest dreams, I envision a meta assistant or assistant orchestrator where, based on a brief, it would create every step of a campaign. For example, for an email campaign you would get:</p>

<ul>
  <li>The email content, including A/B testing and personalization</li>
  <li>The AJO campaign, ready to be activated</li>
  <li>The RTCDP segment for the AJO campaign</li>
  <li>The CJA dashboard for this campaign</li>
</ul>

<h2 id="content-optimizers">Content Optimizers</h2>

<p>If you are like me and come from the data world, you may not be that familiar with the content world. We like to think of AEP as the center of our universe. However, once you learn and understand the predominant role of Adobe Experience Manager (AEM), you realize how big <a href="https://www.pedromonjo.com/2023/09/data-content.html">the world beyond data</a> is. While I am not an AEM expert, I can tell how it plays a key role in big corporations. All my clients use it as their content management system and it is a central component of their digital marketing stack.</p>

<p>It should be no surprise, then, that Adobe has released AI tools specifically designed for content:</p>

<ul>
  <li><a href="https://experienceleague.adobe.com/en/docs/experience-manager-sites-optimizer/content/home">AEM Sites Optimizer</a>. It analyzes and improves the performance of websites built on AEM. Not only does it audit your website, but it can also apply the changes to AEM to improve it.</li>
  <li><a href="https://experienceleague.adobe.com/en/docs/llm-optimizer/using/home">Adobe LLM Optimizer</a>. With the advent of Large Language Models (LLM) and the chat engines in front of them, webmasters want their websites to rank high in the popular LLMs, so that they become the reference of certain prompts. This is called <a href="https://www.pedromonjo.com/2023/09/data-content.html">Generative Engine Optimization</a> (GEO). Well, LLM Optimizer was designed with this goal in mind: help websites become the reference of AI chat assistants.</li>
</ul>

<h2 id="end-user-support">End User Support</h2>

<p>You are all familiar with those chat tools <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 32 32"><defs><style>.cls-1 {fill: #4b4b4b;}</style></defs><path class="cls-1" d="M17.99316,30.06445a1.98415,1.98415,0,0,1-1.48828-.67383L10.82422,23H5.05957A5.0659,5.0659,0,0,1,0,17.93945V7.05957A5.06506,5.06506,0,0,1,5.05957,2H26.93994A5.06548,5.06548,0,0,1,32,7.05957V17.93945A5.06632,5.06632,0,0,1,26.93994,23H20v5.0625a1.98517,1.98517,0,0,1-1.28955,1.86914A2.00829,2.00829,0,0,1,17.99316,30.06445ZM5.05957,4A3.06278,3.06278,0,0,0,2,7.05957V17.93945A3.06361,3.06361,0,0,0,5.05957,21h6.21387a.99954.99954,0,0,1,.74756.33594L18,28.0625V22a.99974.99974,0,0,1,1-1h7.93994A3.06372,3.06372,0,0,0,30,17.93945V7.05957A3.06288,3.06288,0,0,0,26.93994,4Z"></path><g><circle class="cls-1" cx="9.70414" cy="12.5" r="2.09862"></circle><circle class="cls-1" cx="16" cy="12.5" r="2.09862"></circle><circle class="cls-1" cx="22.29586" cy="12.5" r="2.09862"></circle></g></svg> that many websites have. They are mainly call deflectors: they try to help end users who have basic questions, so that they can self-serve and do not need to call the client support center. However, the existing chat tools are very basic. Every time I have tried to use one, I have ended up having to interact with a human, as the tool could not do what I needed.</p>

<p>With <a href="https://experienceleague.adobe.com/en/docs/brand-concierge/content/home">Adobe Brand Concierge</a>, Adobe is entering this space, offering an AI tool to manage end user conversations. This is one of the rare occasions where the Adobe Experience Cloud offers a tool that is specifically designed for the end users, the customers of our customers (anybody remembers Adobe Survey?). This tool is in its infancy, but if it delivers on the promise, we will finally get a chat that works.</p>

<p> </p>

<p><small>Photo by <a href="https://unsplash.com/@boliviainteligente?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">BoliviaInteligente</a> on <a href="https://unsplash.com/photos/a-close-up-of-a-computer-processor-with-many-components-frbBBb2l2SI?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="Opinion" /><summary type="html"><![CDATA[Adobe has been announcing multiple Artificial Intelligence (AI) capabilities as part of the Adobe Experience Cloud. This is just an evolution of Adobe Sensei, the collection of Machine Learning (ML) features that have been released over the last 10+ years. However, with AI things are getting more complicated and it is not always easy to understand where each feature belongs to. I wanted to summarize and classify the features, both for you and me, as our memory is fragile.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-02-15-ai-adobe/ai.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-02-15-ai-adobe/ai.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Thinking is Cheap</title><link href="https://www.pedromonjo.com/2026/02/thinking-is-cheap.html" rel="alternate" type="text/html" title="Thinking is Cheap" /><published>2026-02-01T00:00:00-05:00</published><updated>2026-02-01T00:00:00-05:00</updated><id>https://www.pedromonjo.com/2026/02/thinking-is-cheap</id><content type="html" xml:base="https://www.pedromonjo.com/2026/02/thinking-is-cheap.html"><![CDATA[<p>Imagine the following scenario. You have just purchased a plot with sea views. You are eager to build a house to start enjoying it. However, you want to save money and do not hire an architect to design the house. Instead, you go directly to the construction company. They ask for the blueprint, but you tell them that it is not needed, that they should just look at the houses in the neighborhood and build something similar. Would you do that? I am sure the answer is “no”. So, why do we see this approach so often in technology?</p>

<!--more-->

<h2 id="execution-is-expensive">Execution is Expensive</h2>

<p>While the situation with the house described above is completely imaginary, I have seen this in technology. It goes without saying that they tend to end in fiasco.</p>

<p>I was once assigned to a project, where the customer had already implemented the data architecture of the <a href="https://www.pedromonjo.com/category/platform/">Adobe Experience Platform</a> (AEP). Schemas and datasets had been created in the production sandbox, they were already loading data, and some destinations had been configured. With this part of the implementation done, they asked the business for use cases. To their horror, very few could be delivered with that implementation. The problem was that the IT department had implemented what they thought was the right design for the data, without ever asking the business team for their input.</p>

<p>After multiple discussions with this customer, we made the hard decision to reset the production sandbox. I actually clicked the button, I felt really bad. That probably was $500k (or more) going down the drain.</p>

<p>Unfortunately, this is not an isolated case; I still see this behavior in some of the engagements I have been part of. IT directors argue that a discovery and design phase is a waste of time and money, that it is just a “lift and shift” and they need to show value quickly. I have bad news for them.</p>

<h2 id="thinking-is-cheap">Thinking is Cheap</h2>

<p>While I am not in sales, I work closely with them. A typical hurdle they face when negotiating contracts with customers is that architects, business consultants, and digital strategists are expensive. I beg to differ. I am not going to dispute that our hourly rate is higher than that of a developer, but:</p>

<ul>
  <li>In a healthy project, the number of architect or strategist hours should always be significantly smaller than the total development time. We are in technology, not philosophy.</li>
  <li>Fixing a mistake on a whiteboard or redoing an architecture diagram that does not support the use cases costs a fraction of having to redo an implementation that is not fit for purpose.</li>
  <li>As I have explained multiple times, you must start with the <em>why</em>, and to find this why, you need to spend time thinking before you start developing.</li>
</ul>

<p>I can understand the anxiety to show value. In my post <a href="https://www.pedromonjo.com/2025/01/buy-raspberry-pi.html">Buy a Raspberry Pi</a>, I explain how our customers expect a return on their investment when they buy enterprise-grade software like the Adobe Experience Cloud. However, this should not be an excuse to put the cart before the horse.</p>

<p>In summary, and based on my experience, I can confidently say that “thinking is cheap”.</p>

<p> </p>

<p><small><a href="https://www.pexels.com/photo/photo-of-woman-in-deep-thought-3768144/">Photo by Andrea Piacquadio</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="Opinion" /><summary type="html"><![CDATA[Imagine the following scenario. You have just purchased a plot with sea views. You are eager to build a house to start enjoying it. However, you want to save money and do not hire an architect to design the house. Instead, you go directly to the construction company. They ask for the blueprint, but you tell them that it is not needed, that they should just look at the houses in the neighborhood and build something similar. Would you do that? I am sure the answer is “no”. So, why do we see this approach so often in technology?]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-02-01-thinking-is-cheap/thinking.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-02-01-thinking-is-cheap/thinking.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Support Shopping</title><link href="https://www.pedromonjo.com/2026/01/support-shopping.html" rel="alternate" type="text/html" title="Support Shopping" /><published>2026-01-18T00:00:00-05:00</published><updated>2026-01-18T00:00:00-05:00</updated><id>https://www.pedromonjo.com/2026/01/support-shopping</id><content type="html" xml:base="https://www.pedromonjo.com/2026/01/support-shopping.html"><![CDATA[<p>I have been at Adobe for more than 13 years, and this has been my only job as a consultant. I explain this because I do not have previous experience in other consulting roles. However, during all this time I have regularly seen a behavior that I want to talk about today: support shopping.</p>

<!--more-->

<h2 id="what-is-support-shopping">What is Support Shopping?</h2>

<p>I do not think that there is a standard definition for this concept. In fact, I heard it for the first time from a coworker not that long ago. Since then, though, I have used it when needed and everybody understood me. If I had to provide such a definition, it would be something like:</p>

<blockquote>
  <p>The activity of requesting help from multiple people simultaneously, without telling them that you have also asked others.</p>
</blockquote>

<p>I am sure you have seen this behavior in the past. Someone has a problem with a technology, and they ask their technical representative and their account manager, and open a ticket with support. I understand why our clients do that: they do not always know who to ask or they are in a hurry and need an urgent response. However, there are important downsides.</p>

<h2 id="the-issue-with-support-shopping">The Issue with Support Shopping</h2>

<p>The main problem with this behavior is that the outcome can make things worse.</p>

<p>Let’s consider a scenario where a client wants to know what the best color in a particular scenario is. The client raises the question to the provider, asking four different people. Three reply “blue”, which is the one that the provider has documented as the best color, but one outlier misunderstands the question and says “pink”. It turns out that the client wanted pink as the color, as it is the color they already have. So, they go with pink. Later, when it is clear that this was the wrong color, they will claim that it is the provider’s fault, as they recommended pink.</p>

<p>I want to be very clear about one point: the person who replied “pink” is not to be blamed. Any of us could have done it.</p>

<p>The more I work with internal teams at Adobe, the more I understand the origin of this problem:</p>

<ul>
  <li>Client Care manages dozens, if not hundreds of tickets daily. Very few are problems with the Adobe products, most are related to issues with the client implementation. They do not have the knowledge or the time to research the exact details, and will mostly fall back to the published best practices.</li>
  <li>Customer Success Managers (CSM) are mostly non-technical employees. They understand the technology at a high level, but their role is about value realization, not Java code.</li>
  <li>Account Directors (AD) lead the sales team, not the implementation team.</li>
  <li>Technical Account Managers (TAM) are technical and have the knowledge to support the customers, have access to backend tools, but they are not always asked first or may not have been exposed to the context needed to answer correctly.</li>
  <li>Consultants usually know the technical details of the implementation and most likely they have the best answer, but not all our clients have a consulting engagement.</li>
</ul>

<h2 id="my-solution">My Solution</h2>

<p>OK, we have a problem. What do we do about it?</p>

<p>The starting point is to avoid support shopping altogether. Instead, a process should be agreed with the technology provider. I cannot provide a generic process, as there are too many permutations and I have only seen what we do at Adobe.</p>

<p>What I can explain is what I have done with some customers, where I have been involved as an Enterprise Architect.</p>

<ol>
  <li>All issues should be routed first to me or the TAM. We are in the best position to evaluate the problem.</li>
  <li>If this is something we (EA or TAM) can resolve, we just go ahead and do it.</li>
  <li>If we need someone else, but we know who this other person should be, we engage them.</li>
  <li>If the problem is related to not having a license, we will talk with the AD.</li>
  <li>Finally, if we see this is a product issue, we will help the client open a ticket with Client Care, making sure this ticket has all the relevant information, and we will follow up with Client Care.</li>
</ol>

<p>Let me know in the comments if you have seen this behavior or you have implemented a different solution.</p>

<p> </p>

<p><small>Photo by <a href="https://unsplash.com/@jezar?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Jezael Melgoza</a> on <a href="https://unsplash.com/photos/group-of-people-standing-To5wAJDt1IM?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="Opinion" /><summary type="html"><![CDATA[I have been at Adobe for more than 13 years, and this has been my only job as a consultant. I explain this because I do not have previous experience in other consulting roles. However, during all this time I have regularly seen a behavior that I want to talk about today: support shopping.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-01-18-support-shopping/shopping.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-01-18-support-shopping/shopping.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">AEP Connecting Tissue</title><link href="https://www.pedromonjo.com/2026/01/aep-connecting-tissue.html" rel="alternate" type="text/html" title="AEP Connecting Tissue" /><published>2026-01-04T00:00:00-05:00</published><updated>2026-01-04T00:00:00-05:00</updated><id>https://www.pedromonjo.com/2026/01/aep-connecting-tissue</id><content type="html" xml:base="https://www.pedromonjo.com/2026/01/aep-connecting-tissue.html"><![CDATA[<p>When licensing a tool like the Adobe Experience Platform (AEP), you should expect an implementation effort at the beginning. One area that is often overlooked is the integration between AEP and other tools. Most of my customers deal with it as with any other integration, but to some it has come as a surprise.</p>

<!--more-->

<h2 id="friction-point">Friction Point</h2>

<p>The reason why I wanted to write this post in the first place is to clarify the unnecessary friction that integrating AEP with the rest of the ecosystem has caused. Some of my previous customers had an expectation that this tool can connect to any data source or destination seamlessly, without any additional development.</p>

<p>AEP, like any other advanced software, was built to be as generic as possible. We have thousands of customers, each with a completely different setup. It is impossible to build a piece of software that supports every single case.</p>

<p>As I was preparing this post, I remember my days with <a href="https://www.pedromonjo.com/category/analytics-tips/">Adobe Analytics</a> and <a href="https://www.pedromonjo.com/category/aam/">Adobe Audience Manager</a>, when this was not a major issue. I guess that, since these tools were very specific and <em>simple</em> compared to AEP, such an expectation did not exist. I also assume that Adobe Experience Manager (AEM) requires a large implementation, which allows for developer time to create any custom integration.</p>

<h2 id="out-of-the-box">Out of the Box</h2>

<p>Let’s start with the out-of-the-box integrations that already exist. AEP boasts a large number of <a href="https://experienceleague.adobe.com/en/docs/experience-platform/sources/home">data sources</a> and <a href="https://experienceleague.adobe.com/en/docs/experience-platform/destinations/catalog/overview">destinations</a>.</p>

<p>The catalog has dozens of integrations; I stopped counting a long time ago. Some of the categories that you will see are:</p>

<ul>
  <li>Other Adobe applications</li>
  <li>Advertising platforms</li>
  <li>Cloud storage</li>
  <li>CRM platforms</li>
  <li>Databases</li>
</ul>

<p>Expect more to be added and feel free to suggest new ones to your Adobe representative.</p>

<p>Most of my customers have all they need in the existing catalog. If you find one that suits you, just go ahead and use it. However, be mindful that you cannot change the behavior other than what is available in the configuration wizard.</p>

<h2 id="custom-integrations">Custom Integrations</h2>

<p>This is where the friction I mentioned at the top arises. I have had customers who were annoyed with me when I told them that the integration they wanted to set up was not possible. I would argue that nobody should be surprised when licensing SaaS tools as complex as AEP, when a custom layer needs to be built. In other words, my point is that, if you are implementing AEP, you should expect some level of custom integration, which will require development effort.</p>

<p>These are some examples I have seen where a custom integration was needed:</p>

<ul>
  <li>Source data format needs to be adapted to XDM format to take full advantage of AEP’s features.</li>
  <li>Connecting to a data source needs to follow a particular security standard not currently supported.</li>
  <li>AEP sends the data too fast to a destination.</li>
</ul>

<p>The solutions I have seen in the past can be grouped into two groups: ETLs and microservices. Let me know in the comments if you used other approaches.</p>

<h3 id="etls">ETLs</h3>

<p><a href="https://en.wikipedia.org/wiki/Extract,_transform,_load">Extract-Transform-Load</a> (ETL) tools have existed for a very long time. With them, you can adjust the format of a data source to the format expected by a destination. I do not have the numbers, but I believe that most AEP implementations use ETLs to extract marketing data from internal systems and convert it to XDM or CSV format to be ingested by AEP. The main limitation of an ETL tool is that it usually only supports batch processes.</p>

<p>I would assume that all my customers would have, at least, one ETL tool. To my dismay, I have had customers who refused to use one.</p>

<h3 id="microservice">Microservice</h3>

<p>I am using here the term “microservice” very loosely. I am referring to a custom development that receives data from a source and does all the necessary adjustments so that the destination can receive the data. I know it sounds like an ETL, but there are a couple of major differences:</p>

<ul>
  <li>Microservices can support real-time use cases.</li>
  <li>Microservices can do anything that you can do with a programming language, which is more than what ETLs can usually do.</li>
</ul>

<p>Let me share a few examples of microservices my customers have needed:</p>

<ul>
  <li>The <a href="https://experienceleague.adobe.com/en/docs/experience-platform/destinations/catalog/streaming/http-destination">HTTP API connection</a> was sending the data too fast and in bursts, causing the downstream system to crash. This destination was designed to be as fast as possible, so it is not possible to make it slower. The solution was to build a microservice with a large buffer, to adjust the speed.</li>
  <li>An Adobe Journey Optimizer (AJO) journey required accessing data in a way that AJO custom actions could not support. A microservice was created, which would receive a call from a custom action, retrieve the additional data, and send it in an event back to AEP, which was captured by the journey.</li>
  <li><a href="https://experienceleague.adobe.com/en/docs/journey-optimizer/using/orchestrate-journeys/about-journey-building/using-custom-actions">AJO Custom Actions</a> send calls individually, whereas a particular receiver end required microbatching and a slower rate. A microservice took care of this mismatch.</li>
</ul>

<p>In summary, with AEP (and, I assume, with our competitors), you should always expect to develop a connecting layer around AEP.</p>

<p> </p>

<p><small>Photo by <a href="https://unsplash.com/@barkiple?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">John Barkiple</a> on <a href="https://unsplash.com/photos/assorted-electric-cables-l090uFWoPaI?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="Platform" /><summary type="html"><![CDATA[When licensing a tool like the Adobe Experience Platform (AEP), you should expect an implementation effort at the beginning. One area that is often overlooked is the integration between AEP and other tools. Most of my customers deal with it as with any other integration, but to some it has come as a surprise.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-01-04-aep-connecting-tissue/connections.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2026/2026-01-04-aep-connecting-tissue/connections.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">2025 Retrospective</title><link href="https://www.pedromonjo.com/2025/12/2025-retrospective.html" rel="alternate" type="text/html" title="2025 Retrospective" /><published>2025-12-22T00:00:00-05:00</published><updated>2025-12-22T00:00:00-05:00</updated><id>https://www.pedromonjo.com/2025/12/2025-retrospective</id><content type="html" xml:base="https://www.pedromonjo.com/2025/12/2025-retrospective.html"><![CDATA[<p>Another year comes to a close. Today is my first day of PTO, so I am still not fully feeling it. I have woken up at the usual time because I have a lot to do in the coming days before I go on a trip. However, it is the perfect moment of the year to reflect and look back.</p>

<!--more-->

<h2 id="life">Life</h2>

<p>I think that most of us can agree that the political situation all around the world has seen better days. Most big and influential countries have had their fair share of drama. I have lived in three countries and all three have major issues. While it would be great if I could ignore all those problems, it is impossible, at least for me. I guess I worry too much about what would happen if things go really wrong.</p>

<p>My work at Adobe is going well. Looking back into this year that is ending makes me realize how much I have learned. I feel more and more confident in my role as a manager (part-time), and I like working with my customers, with whom I have a very good working relationship. I still go to the office three days a week, which gives me the opportunity to meet my coworkers.</p>

<p>The most significant change that I have noticed is the continuous disruption caused by AI. I can see how knowing the technology behind AI can have an impact. In fact, I recommend that you learn AI as deeply as you can. I have understood <a href="https://en.wikipedia.org/wiki/Neural_network">neural networks</a> for many years, but the AI tools we have today go way beyond neural networks. Being an expert in prompt engineering will help you with your day-to-day, but if you want to be more employable you need to understand how <a href="https://en.wikipedia.org/wiki/Large_language_model">LLMs</a> work.</p>

<p>I have some vague knowledge and, to catch up a bit with this technology, I have just purchased a new computer with a powerful enough processor and lots of RAM, so I can play with <a href="https://ollama.com/">Ollama</a> and other related tools. I do not think I can ride the AI wave, but at least I will be able to take more advantage of it.</p>

<p>For us working at Adobe, not only do we need to know how to use conversational AI tools, but also know how our own AI tools work: <a href="https://firefly.adobe.com/">Firefly</a>, <a href="https://www.adobe.com/acrobat/generative-ai-pdf.html">AI Assistant for Acrobat Studio</a>, or <a href="https://experienceleague.adobe.com/en/docs/brand-concierge/content/home">Adobe Brand Concierge</a>. I have seen parts of the architecture behind the latter and it is anything but simple.</p>

<h2 id="blog">Blog</h2>

<p>I always want to talk about my blog, as it is an important part of my life. I usually publish every two weeks. It is a lot of effort. For context, a standard post takes around four hours. If I want to make it more complex, with code examples or steps in the UI, it will take even longer. I wish I had more time to write more often.</p>

<h3 id="metrics">Metrics</h3>

<p>Let me share a few metrics with you:</p>

<p><a data-toggle="lightbox" href="https://www.pedromonjo.com/assets/images/originals/2025/2025-12-22-2025-retrospective/2025-basic-metrics.png"><img loading="lazy" src="https://www.pedromonjo.com/assets/images/responsive/2025/2025-12-22-2025-retrospective/2025-basic-metrics-300-c7434b6e5.png" alt="2025 Basic Metrics" srcset="https://www.pedromonjo.com/assets/images/responsive/2025/2025-12-22-2025-retrospective/2025-basic-metrics-300-c7434b6e5.png 1.0x, https://www.pedromonjo.com/assets/images/responsive/2025/2025-12-22-2025-retrospective/2025-basic-metrics-450.0-c7434b6e5.png 1.5x, https://www.pedromonjo.com/assets/images/responsive/2025/2025-12-22-2025-retrospective/2025-basic-metrics-600-c7434b6e5.png 2.0x"></a></p>

<p>I was expecting this drop in numbers, so I am not concerned:</p>

<ul>
  <li>Most of 2025 was flat, except for something weird around October. This means a steady flow of visits, which is what I would expect.</li>
  <li>The metrics in <a href="https://www.pedromonjo.com/2024/12/2024-retrospective.html">2024</a> showed an increase in traffic that did not make sense.</li>
  <li>We should all expect decreases in organic traffic, as AI becomes more prevalent. My customers have already noticed it.</li>
</ul>

<h3 id="countries">Countries</h3>

<p>The top 5 countries are the same as last year:</p>

<ol>
  <li>Singapore: 31.3%</li>
  <li>United States: 26.7%</li>
  <li>India: 14.7%</li>
  <li>United Kingdom: 3.4%</li>
  <li>Canada: 3.2%</li>
</ol>

<p>Surprisingly, Singapore is still at the top. I assume that many people are using a VPN that exits through this country, otherwise I cannot believe that such a small country brings more traffic than the U.S. Where could these visitors come from? China?</p>

<h3 id="traffic-sources">Traffic Sources</h3>

<p>To me, this is the most interesting report to evaluate in 2025. As I said above, AI is replacing search engines at a slow but steady pace. I can already see it in the traffic sources report:</p>

<ol>
  <li>Search Engines: 45.9%</li>
  <li>Types/Bookmarked: 40.5%</li>
  <li>Social Networks: 9.4%</li>
  <li>Other Websites: 4.0%</li>
  <li>Conversational AI: 0.3%</li>
</ol>

<p>These are the OOTB Adobe Analytics classifications and it is important that you understand them:</p>

<ul>
  <li>Adobe only added “Conversational AI” in the last few months.</li>
  <li>Before adding “Conversational AI”, those tools showed up under “Other Websites”.</li>
  <li>Search engines often show an AI-generated response to your query; if you click on it, the referrer will still be classified as “Search Engine”.</li>
</ul>

<p><a data-toggle="lightbox" href="https://www.pedromonjo.com/assets/images/originals/2025/2025-12-22-2025-retrospective/ai-referrals.png"><img loading="lazy" src="https://www.pedromonjo.com/assets/images/responsive/2025/2025-12-22-2025-retrospective/ai-referrals-300-76f6d45d7.png" alt="AI referrals" srcset="https://www.pedromonjo.com/assets/images/responsive/2025/2025-12-22-2025-retrospective/ai-referrals-300-76f6d45d7.png 1.0x, https://www.pedromonjo.com/assets/images/responsive/2025/2025-12-22-2025-retrospective/ai-referrals-450.0-76f6d45d7.png 1.5x, https://www.pedromonjo.com/assets/images/responsive/2025/2025-12-22-2025-retrospective/ai-referrals-600-76f6d45d7.png 2.0x"></a></p>

<p>The good news is that conversational AI tools believe that my blog is a reliable source of information.</p>

<h2 id="looking-into-2026">Looking into 2026</h2>

<p>I hope that the new year comes with more positive news. As I said above, I am not very happy with how 2025 has behaved.</p>

<p>For this blog, I will start posting more about AI in relation to Adobe technologies. I need to learn more and, as I do, I will share my knowledge with you. That being said, I want to mention that, in my humble opinion, there has been too much hype around AI, and <a href="https://duckduckgo.com/?q=ai+hype&amp;ia=web">I am not the only one thinking along these lines</a>.</p>

<p>Happy 2026!</p>

<p> </p>

<p><small>Photo by <a href="https://unsplash.com/@boliviainteligente?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">BoliviaInteligente</a> on <a href="https://unsplash.com/photos/year-2026-displayed-on-cubes-1AgT_-D6SiU?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="Opinion" /><summary type="html"><![CDATA[Another year comes to a close. Today is my first day of PTO, so I am still not fully feeling it. I have woken up at the usual time because I have a lot to do in the coming days before I go on a trip. However, it is the perfect moment of the year to reflect and look back.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2025/2025-12-22-2025-retrospective/2026.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2025/2025-12-22-2025-retrospective/2026.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">SPAs and Attribution</title><link href="https://www.pedromonjo.com/2025/12/spas-attribution.html" rel="alternate" type="text/html" title="SPAs and Attribution" /><published>2025-12-07T00:00:00-05:00</published><updated>2025-12-07T00:00:00-05:00</updated><id>https://www.pedromonjo.com/2025/12/spas-attribution</id><content type="html" xml:base="https://www.pedromonjo.com/2025/12/spas-attribution.html"><![CDATA[<p>I wanted to share an issue one of my customers recently encountered. They called me saying that they had found issues in Adobe Analytics attribution and they were questioning the quality of the data. After digging into it, we found that the problem was due to their Single Page Application (SPA) implementation. If your website uses any SPA technology, this post interests you.</p>

<!--more-->

<h2 id="page-views">Page Views</h2>

<p>In traditional websites (i.e., those that do not use any SPA technology), every page view is a full page download. After clicking on internal links, the browser unloads the previous page, releasing all the resources, and loads the new page. From a technical perspective, it is very clear what a page view is, and you can easily configure Web SDK to send data with every new page view.</p>

<p>However, with SPAs, only the first page is a full download. All subsequent clicks trigger the download of only the content specific to the new page. The rest of the page stays the same: menu, header, side bars, footer… In other words, while the end users feel like they are moving from page to page, technically, the browser does not unload any pages nor release any resources, and thinks it is always on the first page. Page view events are sent to Adobe Analytics or <a href="https://experienceleaguecommunities.adobe.com/t5/adobe-experience-platform-blogs/introduction-to-the-aep-edge-network/ba-p/658579">Adobe Experience Platform Edge</a> based on arbitrary rules included in the SPA controller, typically when more than 50% of the content of the page changes.</p>

<p>This behavior has a direct impact on the page metadata, in particular, the previous URL or referrer, which is accessible through <code class="language-plaintext highlighter-rouge">document.referrer</code>. Web SDK uses this parameter for the previous page URL.</p>

<p>In the case of traditional websites, this parameter is automatically updated with every page refresh, and, as expected, it holds the URL to the previous page. However, in SPAs, since the browser only fully loads the first page, the referrer does not get updated again with every click.</p>

<h2 id="the-attribution-problem-in-spas">The Attribution Problem in SPAs</h2>

<!-- markdownlint-capture -->
<!-- markdownlint-disable MD034 MD055 MD056 -->

<p>Let’s consider a scenario with the following Adobe Analytics <a href="https://www.pedromonjo.com/2016/09/marketing-channels-fundamentals.html">Marketing Channels</a> rules:</p>

<table class="table table-bordered">
  <thead>
    <tr>
      <th style="text-align: right">Order</th>
      <th>Channel</th>
      <th>Rules</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: right">1</td>
      <td>Paid Search</td>
      <td>Referrer is search engine <br /> AND campaign ID is present</td>
    </tr>
    <tr>
      <td style="text-align: right">2</td>
      <td>Natural Search</td>
      <td>Referrer is search engine</td>
    </tr>
    <tr>
      <td style="text-align: right">3</td>
      <td>Paid Social</td>
      <td>Referrer is social site <br /> AND campaign ID is present</td>
    </tr>
    <tr>
      <td style="text-align: right">4</td>
      <td>Natural Social</td>
      <td>Referrer is social site</td>
    </tr>
  </tbody>
</table>

<p>Next, consider the following chain of events from an end user perspective, where the website is an SPA:</p>

<table class="table table-bordered">
  <thead>
    <tr>
      <th style="text-align: right">#</th>
      <th>Event</th>
      <th>Referrer</th>
      <th>Campaign ID</th>
      <th>Marketing Channel</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: right">1</td>
      <td>Searches in Google</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: right">2</td>
      <td>Clicks on paid link and lands on website</td>
      <td>https://www.google.com/</td>
      <td>a1b2c3d4e5</td>
      <td>Paid Search</td>
    </tr>
    <tr>
      <td style="text-align: right">3</td>
      <td>Clicks on an internal link</td>
      <td>https://www.google.com/</td>
      <td> </td>
      <td>Natural Search</td>
    </tr>
    <tr>
      <td style="text-align: right">4</td>
      <td>Clicks on another internal link</td>
      <td>https://www.google.com/</td>
      <td> </td>
      <td>Natural Search</td>
    </tr>
  </tbody>
</table>

<p>As you can see, on the landing page, the marketing channel was correctly identified, but it then gets updated to a wrong value from the second page onwards. Any conversion further down the line would then be attributed to “Natural Search”, when it should have been attributed to “Paid Search”.</p>

<p>If you think that Adobe Analytics has enough information to know that it is an internal link, think again. Consider this alternative series of events:</p>

<table class="table table-bordered">
  <thead>
    <tr>
      <th style="text-align: right">#</th>
      <th>Event</th>
      <th>Referrer</th>
      <th>Campaign ID</th>
      <th>Marketing Channel</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: right">1</td>
      <td>Searches in Google</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: right">2</td>
      <td>Clicks on paid link and lands on website</td>
      <td>https://www.google.com/</td>
      <td>a1b2c3d4e5</td>
      <td>Paid Search</td>
    </tr>
    <tr>
      <td style="text-align: right">3</td>
      <td>Clicks on back button and lands on Google again</td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: right">4</td>
      <td>Clicks on organic link and lands on website</td>
      <td>https://www.google.com/</td>
      <td> </td>
      <td>Natural Search</td>
    </tr>
    <tr>
      <td style="text-align: right">5</td>
      <td>Clicks on an internal link</td>
      <td>https://www.google.com/</td>
      <td> </td>
      <td>Natural Search</td>
    </tr>
    <tr>
      <td style="text-align: right">6</td>
      <td>Clicks on another internal link</td>
      <td>https://www.google.com/</td>
      <td> </td>
      <td>Natural Search</td>
    </tr>
  </tbody>
</table>

<p>With this new scenario, although events 5 and 6 should not carry any marketing channel value, the value they get is actually correct, and attribution would be fine. However, the crucial question is: how can Adobe Analytics know the difference between this example and the one before? It cannot. They both look exactly the same, as Google does not send any data to Adobe Analytics.</p>

<p>This problem does not exist on a traditional website. The clicks on internal links will carry the correct URL in the referrer. They will be correctly identified as internal URLs and will not generate new Marketing Channel values. I want to also clarify that this explanation refers to last-touch attribution; first-touch attribution should always be correct.</p>

<!-- markdownlint-restore -->

<h2 id="the-solution">The Solution</h2>

<p>Ideally, you would want to tweak the Marketing Channels rules to deal with this scenario. Unfortunately, this is not always possible. Marketing Channels rules only evaluate the current hit, with no context of previous hits. Crucial information needed to correctly identify the marketing channel is present in these previous hits. For example, in event 3 of the second table above, the Marketing Channels engine only sees that the referrer is Google.</p>

<p>The best solution is to modify your Web SDK implementation, either manually or through Adobe Tags. Your rules should keep track of the previous page that was shown and use it to populate <code class="language-plaintext highlighter-rouge">web &gt; webReferrer &gt; URL</code> (see the <a href="https://experienceleague.adobe.com/en/docs/experience-platform/xdm/field-groups/event/web-details">Web Details field group</a>) with the correct, internal URL. If you use Adobe Tags, you will probably need a Data Element.</p>

<p>For more information, read this Experience League post: <a href="https://experienceleaguecommunities.adobe.com/t5/adobe-analytics-blogs/web-sdk-part-4-avoiding-banana-peels-and-other-things-that-can/ba-p/644750">Web SDK Part 4: Avoiding Banana Peels</a> (Banana Peel #6).</p>

<p> </p>

<p><small>Photo by <a href="https://unsplash.com/@alexbertha?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Alex Bertha</a> on <a href="https://unsplash.com/photos/woman-in-water-pool-Jyg7xHRmXiU?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="Analytics Tips" /><summary type="html"><![CDATA[I wanted to share an issue one of my customers recently encountered. They called me saying that they had found issues in Adobe Analytics attribution and they were questioning the quality of the data. After digging into it, we found that the problem was due to their Single Page Application (SPA) implementation. If your website uses any SPA technology, this post interests you.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2025/2025-12-07-spas-attribution/spa.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2025/2025-12-07-spas-attribution/spa.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>