<?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-07-26T19:03:14-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">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><entry><title type="html">Unified Profile Service and Unified Identity Service</title><link href="https://www.pedromonjo.com/2025/11/ups-uis.html" rel="alternate" type="text/html" title="Unified Profile Service and Unified Identity Service" /><published>2025-11-23T00:00:00-05:00</published><updated>2025-11-23T00:00:00-05:00</updated><id>https://www.pedromonjo.com/2025/11/ups-uis</id><content type="html" xml:base="https://www.pedromonjo.com/2025/11/ups-uis.html"><![CDATA[<p>If you have been following my posts, you now understand the concept of profile projection, and the roles of the Unified Identity Service (UIS) and the Unified Profile Service (UPS). However, for others who are not familiar with these concepts, I am going to explain them from a different angle.</p>

<!--more-->
<style type="text/css">
   ol { list-style-type: decimal; }
   ol ol {list-style-type: lower-alpha; }
</style>

<p>As usual, since I have been writing about the Adobe Experience Platform (AEP), I will use the following diagram as reference:</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>

<p>And just to set the stage, today I will describe the “Profile Store” and the “Identity Store” boxes in the previous diagram.</p>

<h2 id="unified-profile-service">Unified Profile Service</h2>

<p>This is the simplest of the two, but before I get into the details, I will start by explaining what it is <em>not</em>. It is very tempting to think that this is a relational database where each row corresponds to a profile, with the columns being the schema parameters. Let me assure you that this is not the case. There is no SQL database behind the scenes, nor are we issuing SELECTs, UPDATEs or INSERTs. If this were the case, <a href="https://www.pedromonjo.com/2022/10/profile-collapse.html">profile collapses</a> would be totally unsolvable, whereas in reality, there are various solutions.</p>

<p>Instead, you have to think of UPS as a <strong>store of fragments</strong>, which behaves as follows based on the type of dataset:</p>

<ul>
  <li>Profile datasets: it keeps the last update received from the data source, for each primary identity.</li>
  <li>Events datasets: it keeps all events whose TTL has not been reached.</li>
</ul>

<p>All these fragments are keyed by their primary identity. Think of it as a key-value pair database, where the key is the primary identity and the value is an array of fragments. This is why it is so important to decide on the primary identity in a schema: it will be the key used to find fragments in UPS. The same applies to the <code class="language-plaintext highlighter-rouge">primary</code> field in the <code class="language-plaintext highlighter-rouge">identityMap</code>.</p>

<p><a data-toggle="lightbox" href="https://www.pedromonjo.com/assets/images/originals/2025/2025-11-23-ups-uis/aep-primary-identity.png"><img loading="lazy" src="https://www.pedromonjo.com/assets/images/responsive/2025/2025-11-23-ups-uis/aep-primary-identity-300-6752bbed5.png" alt="AEP Primary Identity" srcset="https://www.pedromonjo.com/assets/images/responsive/2025/2025-11-23-ups-uis/aep-primary-identity-300-6752bbed5.png 1.0x, https://www.pedromonjo.com/assets/images/responsive/2025/2025-11-23-ups-uis/aep-primary-identity-450.0-6752bbed5.png 1.5x, https://www.pedromonjo.com/assets/images/responsive/2025/2025-11-23-ups-uis/aep-primary-identity-600-6752bbed5.png 2.0x"></a></p>

<p>You can also see how time plays a role: as new profile fragments are received from data sources, previous fragments are deleted, and every day, events whose TTL has been reached are deleted. Remember that the <a href="https://www.pedromonjo.com/2023/05/data-lake.html">Data Lake</a> keeps all data until the batch is deleted.</p>

<p>I want you to pause and see how in this database, a fully formed profile is nowhere to be found. All you have are scattered pieces of information.</p>

<h2 id="unified-identity-service">Unified Identity Service</h2>

<p>Now that we have all the previous fragments, how do we know which need to be combined to project a profile?</p>

<p>If there is only one identity involved, the solution is simple. However, in scenarios where there are multiple identities associated with a single profile, we need a place to <strong>store linked identities</strong>. This is the role of the UIS. Think of it as a specialized storage that keeps track of namespaces, identities, and links between identities. I like how HubSpot depicts this identity graph:</p>

<p><a href="https://blog.hubspot.com/hs-fs/hubfs/Google%20Drive%20Integration/What%20Is%20an%20Identity%20Graph%3F%20[The%20Plain-English%20Guide]-1.png" data-toggle="lightbox"><img src="https://blog.hubspot.com/hs-fs/hubfs/Google%20Drive%20Integration/What%20Is%20an%20Identity%20Graph%3F%20[The%20Plain-English%20Guide]-1.png" alt="Hubspot Identity Graph" width="300" /></a></p>

<p>In AEP, you can view many details of UIS in the “Identities” menu:</p>

<p><a data-toggle="lightbox" href="https://www.pedromonjo.com/assets/images/originals/2025/2025-11-23-ups-uis/aep-identities.png"><img loading="lazy" src="https://www.pedromonjo.com/assets/images/responsive/2025/2025-11-23-ups-uis/aep-identities-300-49a433dac.png" alt="AEP Identities" srcset="https://www.pedromonjo.com/assets/images/responsive/2025/2025-11-23-ups-uis/aep-identities-300-49a433dac.png 1.0x, https://www.pedromonjo.com/assets/images/responsive/2025/2025-11-23-ups-uis/aep-identities-450.0-49a433dac.png 1.5x, https://www.pedromonjo.com/assets/images/responsive/2025/2025-11-23-ups-uis/aep-identities-600-49a433dac.png 2.0x"></a></p>

<p>While UPS cares only about primary identities, UIS wants to know about additional identities, those not marked as primary. As records are ingested from data sources, UIS takes the following steps:</p>

<ol>
  <li>Checks that the primary identity is present; if it is not, rejects the record.</li>
  <li>If there is an additional identity:
    <ol>
      <li>If the identity is already in another graph in the database, both graphs will be collapsed into a single graph.</li>
      <li>If the identity is new, it will be added to the store and linked to the primary identity in the record.</li>
    </ol>
  </li>
</ol>

<p>You will have noticed in the screenshot above that the minimum size of the identity graph is 2. Although you could have, theoretically, a graph with just one node, it does not make sense to store it. Only when two or more nodes are required is a graph created. As a consequence, 2.b above may also create a new graph.</p>

<p>UIS keeps track of where the links between identities came from and when these links were stored in the graph. This key feature has two consequences:</p>

<ul>
  <li>If you delete a dataset or a batch in a dataset, all links between identities that were formed from this dataset or batch will be deleted. I have built solutions around this capability to un-collapse profiles.</li>
  <li>The <a href="https://www.pedromonjo.com/2025/08/identity-graph-linking-rules.html">Identity Graph Linking Rules</a> feature could be implemented, as it can detect the age of a link between identities.</li>
</ul>

<h2 id="where-is-the-profile">Where is the Profile?</h2>

<p>So, we have fragments and identity graphs, not profiles. However, we always talk about a profile. How do we get it?</p>

<p>I will not explain it here, as I wrote a blog post where I described the process step by step: <a href="https://www.pedromonjo.com/2025/04/profile-projection.html">profile projection</a>. I recommend that, if you have not read it, you do so now. Both posts will make a lot of sense when read together.</p>

<p> </p>

<p><small><a href="https://www.pexels.com/photo/group-of-diverse-young-people-with-different-appearances-6238122/">Photo by Monstera Production</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="Platform" /><summary type="html"><![CDATA[If you have been following my posts, you now understand the concept of profile projection, and the roles of the Unified Identity Service (UIS) and the Unified Profile Service (UPS). However, for others who are not familiar with these concepts, I am going to explain them from a different angle.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2025/2025-11-23-ups-uis/identities.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2025/2025-11-23-ups-uis/identities.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Obsession with Lighthouse Scores and GEO</title><link href="https://www.pedromonjo.com/2025/11/obsession-lighthouse-scores-geo.html" rel="alternate" type="text/html" title="Obsession with Lighthouse Scores and GEO" /><published>2025-11-02T00:00:00-04:00</published><updated>2025-11-02T00:00:00-04:00</updated><id>https://www.pedromonjo.com/2025/11/obsession-lighthouse-scores-geo</id><content type="html" xml:base="https://www.pedromonjo.com/2025/11/obsession-lighthouse-scores-geo.html"><![CDATA[<p>In my post on <a href="https://www.pedromonjo.com/2024/12/flicker-personalization-speed.html">Flicker, Personalization and Page Speed</a>, I mentioned the obsession with the Google Lighthouse scores. The LinkedIn comments I received suggested that I was not alone in my views. I planned to write another post, deepening my point of view, but recent developments have significantly changed the landscape.</p>

<!--more-->

<h2 id="summary-of-the-past">Summary of the Past</h2>

<p>Before I explain what is changing, I will start with what I have seen in the last few years.</p>

<p>One of the approximately <a href="https://duckduckgo.com/?q=google+200+parameters">200 ranking factors that Google uses in its ranking algorithm</a> is page load time. Let me repeat that again: only one out of ~200 parameters that Google considers in its algorithm is the time the page takes to load.</p>

<p>This has led hordes of zealots to take full control of websites and demanded major changes to achieve some absurd number. These people would not try to find compromises. The consequences have been noticeable in digital marketing: the capability to personalize websites without flicker has diminished. I have been in my fair share of <em>battles</em> with them and I was never able to win. 😞</p>

<p>I never believed that such extortionate measures were positive. I agree that a website that takes 10+ seconds to load on a desktop connected to a fiber connection offers a very bad experience. However, those 10 seconds on a patchy 3G connection are to be expected. All my customers are well-known companies, who are usually searched for by name. In this case, the brand is so strong that a slightly slower website would not be noticeable in the search results. In other words, site speed, while necessary, is not a deal breaker.</p>

<h2 id="the-new-kid-in-town">The New Kid in Town</h2>

<p>In the last few years, generative AI has taken the market by storm. So much so that one of the immediate consequences has been that <a href="https://www.forbes.com/sites/torconstantino/2025/04/14/the-60-problem---how-ai-search-is-draining-your-traffic/">organic search traffic to websites has dropped significantly</a>. People are changing the way they search for information, especially the younger generations. Instead of using a few, carefully selected keywords in a search engine, you now describe your problem in detail and the generative AI platform replies with all the information you need.</p>

<p>The first consequence is that fewer prospects, although more qualified, will land on your websites. With search engines, we had to manually check multiple links until we found what we wanted. Now, the generative AI tool of choice will do that for us automatically by reviewing multiple pages. Only when the prospect is ready to take the next step, they will click on a reference to your website.</p>

<p>Another consequence is that this new technology will force a change in how digital marketing teams distribute their budgets. There is a new discipline that will soon become a significant part of those marketing efforts: <a href="https://www.pedromonjo.com/2025/09/from-seo-to-geo.html">Generative Engine Optimization</a> (GEO). Not only will you want your website to score high in search engines, but you will also need to be the reference used by Large Language Models (LLMs) when providing a response to a prompt relevant to your business. Adobe has just launched a product called <a href="https://experienceleague.adobe.com/en/docs/llm-optimizer/using/essentials/overview">LLM Optimizer</a> precisely with this goal in mind. As a clarification, LLMs are the backbone of generative AI tools.</p>

<h2 id="my-point-of-view">My Point of View</h2>

<p>In my opinion, and given all that I am seeing and my experience, this is what I expect for the near future:</p>

<ul>
  <li><strong>Content will still be king</strong>. That was an expression used with SEO, but with GEO, it will even be more important. Quality content is what an LLM will use to select a website.</li>
  <li>While Gemini and other LLMs rely on Google data, and thus, page speed will still matter somewhat, not all successful engines will use Google’s index.</li>
  <li>LLMs have a different set of parameters to evaluate whether a website is relevant or not. I doubt page load time will be one.</li>
  <li>There will be a tension between SEO and SEM on the one side, and GEO on the other. I expect <strong>GEO to win</strong>.</li>
  <li>Page speed will always be important, within reasonable expectations. However, the pursuit of perfect <strong>Google Lighthouse scores will become irrelevant</strong>.</li>
  <li>The aggressive approach that many companies have taken to achieve these perfect scores will have to be backtracked to allow <strong>personalization and optimization tools</strong> to do their job.</li>
</ul>

<p>In summary, being able to <strong>use all digital marketing tools at their full capacity</strong> to increase conversion will be more important than ever. Companies will have a smaller window of opportunity to convince a new visitor to stay and convert.</p>

<p>What is your point of view? Will we still have to battle with impossible page speed requirements or will we get back all the tools we need to convince prospects?</p>

<p> </p>

<p><small><a href="https://www.pexels.com/photo/white-and-black-lighthouse-near-the-blue-sea-6006948/">Photo by Tom D’Arby</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="Opinion" /><summary type="html"><![CDATA[In my post on Flicker, Personalization and Page Speed, I mentioned the obsession with the Google Lighthouse scores. The LinkedIn comments I received suggested that I was not alone in my views. I planned to write another post, deepening my point of view, but recent developments have significantly changed the landscape.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2025/2025-11-02-obsession-lighthouse-scores-geo/lighthouse.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2025/2025-11-02-obsession-lighthouse-scores-geo/lighthouse.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Profile Merge Policies</title><link href="https://www.pedromonjo.com/2025/10/profile-merge-policies.html" rel="alternate" type="text/html" title="Profile Merge Policies" /><published>2025-10-19T00:00:00-04:00</published><updated>2025-10-19T00:00:00-04:00</updated><id>https://www.pedromonjo.com/2025/10/profile-merge-policies</id><content type="html" xml:base="https://www.pedromonjo.com/2025/10/profile-merge-policies.html"><![CDATA[<p>I have been dodging the profile merge policies bullet for quite some time, mainly because I never needed to dive deep into it. In fact, I still inadvertently say <a href="https://www.pedromonjo.com/2019/09/introduction-profile-merge-rules.html">profile merge rules</a>, from my <a href="https://www.pedromonjo.com/category/aam/">Adobe Audience Manager</a> days. However, this week I needed to provide a solution that involved creating a new merge policy. So it is high time I explained this capability.</p>

<!--more-->

<h2 id="profile-projection">Profile Projection</h2>

<p>Unless you know very well how a profile gets projected, before you continue reading this post, I highly recommend that you read my post on <a href="https://www.pedromonjo.com/2025/04/profile-projection.html">profile projection</a>. The summary of the process is:</p>

<ol>
  <li>A profile is requested for a given identity.</li>
  <li>The identity graph is queried to get all identities linked to the one provided.</li>
  <li>The profile store is queried to get all the fragments with these identities.</li>
  <li>The fragments are combined following a set of rules.</li>
  <li>The projected profile is returned.</li>
</ol>

<p>In the rest of the post, I will refer to “step [X]”, where X is one of the steps in the process above.</p>

<p>Although I only briefly explained it in my other post, merge policies govern this process. So much so that one of the parameters of the first step is the merge policy to use. Let’s see how this functionality works.</p>

<h2 id="id-stitching">ID Stitching</h2>

<p>One of the parameters that you have to configure when creating a new merge policy is how identities are stitched.</p>

<p><a data-toggle="lightbox" href="https://www.pedromonjo.com/assets/images/originals/2025/2025-10-19-profile-merge-policies/merge-policy-id-stitching.png"><img loading="lazy" src="https://www.pedromonjo.com/assets/images/responsive/2025/2025-10-19-profile-merge-policies/merge-policy-id-stitching-300-5aaa907ea.png" alt="Merge Policy ID Stitching" srcset="https://www.pedromonjo.com/assets/images/responsive/2025/2025-10-19-profile-merge-policies/merge-policy-id-stitching-300-5aaa907ea.png 1.0x, https://www.pedromonjo.com/assets/images/responsive/2025/2025-10-19-profile-merge-policies/merge-policy-id-stitching-450.0-5aaa907ea.png 1.5x, https://www.pedromonjo.com/assets/images/responsive/2025/2025-10-19-profile-merge-policies/merge-policy-id-stitching-600-5aaa907ea.png 2.0x"></a></p>

<h3 id="none">None</h3>

<p>With this value, the profile projection process completely skips step [2] (querying the identity graph). Yes, I know, it sounds weird, especially when one of the main features of the Adobe Experience Platform (AEP) is to stitch identities. However, in some scenarios, it can be very useful.</p>

<p>Let me try to explain it differently, as I am sure some may not have understood it. When you set “ID Stitching” to “None”, and you project a profile, the algorithm will only use the identity that you have supplied in step [1] to look for profile fragments in step [3].</p>

<p>A typical use case when this value becomes very useful is for some scenarios of <a href="https://www.pedromonjo.com/2022/10/profile-collapse.html">profile collapse</a>. Sometimes you do not need the full profile, only the profile fragments that have been submitted with one of the identities. In a way, with this approach, you “uncollapse” the profile. Remember that the profile is not stored anywhere; the profile store only keeps fragments.</p>

<h3 id="private-graph">Private Graph</h3>

<p>This is the default value. In step [2], the algorithm makes a call to the identity store. The identity store subsequently returns all other identities that have been linked to the identity given in step [1].</p>

<h2 id="default-and-edge">Default and Edge</h2>

<p>Let’s move on to the next two parameters.</p>

<p><a data-toggle="lightbox" href="https://www.pedromonjo.com/assets/images/originals/2025/2025-10-19-profile-merge-policies/merge-policy-default-edge.png"><img loading="lazy" src="https://www.pedromonjo.com/assets/images/responsive/2025/2025-10-19-profile-merge-policies/merge-policy-default-edge-300-bdd33b735.png" alt="Default and Edge Merge Policies" srcset="https://www.pedromonjo.com/assets/images/responsive/2025/2025-10-19-profile-merge-policies/merge-policy-default-edge-300-bdd33b735.png 1.0x, https://www.pedromonjo.com/assets/images/responsive/2025/2025-10-19-profile-merge-policies/merge-policy-default-edge-450.0-bdd33b735.png 1.5x, https://www.pedromonjo.com/assets/images/responsive/2025/2025-10-19-profile-merge-policies/merge-policy-default-edge-600-bdd33b735.png 2.0x"></a></p>

<h3 id="default-merge-policy">Default Merge Policy</h3>

<p>There always needs to be a default merge policy, which is the one that will be used if, in step [1], no merge policy is supplied. AEP provides one, but you can change it to one of your own.</p>

<h3 id="active-on-edge-merge-policy">Active-On-Edge Merge Policy</h3>

<p>When the <a href="https://www.pedromonjo.com/2025/03/aep-edge-network.html">AEP Edge Network</a> requests a profile to the core, the process is no different, and it also has to supply a merge policy. It could use the default merge policy, but you have the flexibility to use a different one, if your use case requires it. Since the <a href="https://www.pedromonjo.com/2022/04/edge-network-server-api.html">Edge Network Server API</a> does not have the capability to supply a merge policy, you have to predefine it.</p>

<h2 id="merge-method">Merge Method</h2>

<p>The next configuration parameter that you need to set is the merge method.</p>

<p><a data-toggle="lightbox" href="https://www.pedromonjo.com/assets/images/originals/2025/2025-10-19-profile-merge-policies/merge-policy-method.png"><img loading="lazy" src="https://www.pedromonjo.com/assets/images/responsive/2025/2025-10-19-profile-merge-policies/merge-policy-method-300-fa44a2f21.png" alt="Merge Policies Methods" srcset="https://www.pedromonjo.com/assets/images/responsive/2025/2025-10-19-profile-merge-policies/merge-policy-method-300-fa44a2f21.png 1.0x, https://www.pedromonjo.com/assets/images/responsive/2025/2025-10-19-profile-merge-policies/merge-policy-method-450.0-fa44a2f21.png 1.5x, https://www.pedromonjo.com/assets/images/responsive/2025/2025-10-19-profile-merge-policies/merge-policy-method-600-fa44a2f21.png 2.0x"></a></p>

<p>This method governs how step [4] works. The output of step [3] is a bunch of fragments. However, fragments can overlap; that is, some attributes may be present in multiple fragments. If this happens, which fragments prevail?</p>

<h3 id="timestamp-ordered">Timestamp Ordered</h3>

<p>All fragments include a timestamp of when they were last updated in AEP. If you select this option (the most common), AEP will choose the fragment with the timestamp closest to now, irrespective of where it came from. This is the most common option; in fact, I have not seen any customer use the next value.</p>

<h3 id="dataset-precedence">Dataset Precedence</h3>

<p>Instead of relying on the last fragment update to take precedence, you define a priority of datasets. So, if there is a collision between fragments, AEP will choose the one coming from the dataset highest in the hierarchy.</p>

<p>This capability can be useful when you have datasets coming from various sources, some more reputable than others. For example, you may be ingesting data from your CRM and from 3<sup>rd</sup>-party data providers. If there is any overlap between these data sources, you will likely want your own data, which you control, to take precedence over other sources, which are out of your control.</p>

<p>When you select this option, the UI shows you the datasets, which you have to choose and order. You have to do it for both profile and event datasets. One feature that you also get with this option is that you can leave out some datasets from this merge policy. In other words, you can create a merge policy that will only show a partial profile. I am not sure when I would leave out datasets, but it is always good to have options.</p>

<h2 id="usage">Usage</h2>

<p>OK, now you have your shiny, custom merge policies. How do you use them?</p>

<h3 id="profile-ui">Profile UI</h3>

<p>The first place where you can use them is when you want to browse some profiles in the UI. You may have already noticed that the first parameter is the merge policy, although you probably have ignored it so far. You can remove the default value and select a new one. I find this option very interesting to see how different merge policies generate different profiles.</p>

<p><a data-toggle="lightbox" href="https://www.pedromonjo.com/assets/images/originals/2025/2025-10-19-profile-merge-policies/merge-policy-profile-ui.png"><img loading="lazy" src="https://www.pedromonjo.com/assets/images/responsive/2025/2025-10-19-profile-merge-policies/merge-policy-profile-ui-300-26c79a994.png" alt="Merge Policy Selection in Profile UI" srcset="https://www.pedromonjo.com/assets/images/responsive/2025/2025-10-19-profile-merge-policies/merge-policy-profile-ui-300-26c79a994.png 1.0x, https://www.pedromonjo.com/assets/images/responsive/2025/2025-10-19-profile-merge-policies/merge-policy-profile-ui-450.0-26c79a994.png 1.5x, https://www.pedromonjo.com/assets/images/responsive/2025/2025-10-19-profile-merge-policies/merge-policy-profile-ui-600-26c79a994.png 2.0x"></a></p>

<h3 id="segmentation">Segmentation</h3>

<p>While the profile UI has its uses, when you can really take advantage of merge policies is in segmentation. This means that, when evaluating segments, the profiles sent for activation will be generated by using the selected merge policy.</p>

<p>In order to configure this parameter, in the new audience interface, you should see a gear icon. I had never used it until now. When you click on it, you get a few configuration parameters, one of which is the merge policy.</p>

<p><a data-toggle="lightbox" href="https://www.pedromonjo.com/assets/images/originals/2025/2025-10-19-profile-merge-policies/merge-policy-segmentation.png"><img loading="lazy" src="https://www.pedromonjo.com/assets/images/responsive/2025/2025-10-19-profile-merge-policies/merge-policy-segmentation-300-143bb7f64.png" alt="Merge Policy in Segmentation" srcset="https://www.pedromonjo.com/assets/images/responsive/2025/2025-10-19-profile-merge-policies/merge-policy-segmentation-300-143bb7f64.png 1.0x, https://www.pedromonjo.com/assets/images/responsive/2025/2025-10-19-profile-merge-policies/merge-policy-segmentation-450.0-143bb7f64.png 1.5x, https://www.pedromonjo.com/assets/images/responsive/2025/2025-10-19-profile-merge-policies/merge-policy-segmentation-600-143bb7f64.png 2.0x"></a></p>

<h3 id="profile-api">Profile API</h3>

<p>Being AEP API-first, the <a href="https://experienceleague.adobe.com/en/docs/experience-platform/profile/api/entities">Profile API</a> also supports passing a merge policy. In fact, if you read the documentation under “retrieve an entity”, you get the following explanation:</p>

<blockquote>
  <p>Additionally, usage of the following query parameter is <em>highly</em> recommended:</p>

  <ul>
    <li><code class="language-plaintext highlighter-rouge">mergePolicyId</code>: The ID of the merge policy you want to filter the data with. If no merge policy is specified, your organization’s default merge policy will be used.</li>
  </ul>
</blockquote>

<p> </p>

<p><small><a href="https://www.pexels.com/photo/scrabble-letters-spelling-rules-on-a-wooden-table-19856614/">Photo by Markus Winkler</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="Platform" /><summary type="html"><![CDATA[I have been dodging the profile merge policies bullet for quite some time, mainly because I never needed to dive deep into it. In fact, I still inadvertently say profile merge rules, from my Adobe Audience Manager days. However, this week I needed to provide a solution that involved creating a new merge policy. So it is high time I explained this capability.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2025/2025-10-19-profile-merge-policies/policies.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2025/2025-10-19-profile-merge-policies/policies.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Conversion Funnel and Adobe Tools</title><link href="https://www.pedromonjo.com/2025/10/conversion-funnel-adobe-tools.html" rel="alternate" type="text/html" title="Conversion Funnel and Adobe Tools" /><published>2025-10-05T00:00:00-04:00</published><updated>2025-10-05T00:00:00-04:00</updated><id>https://www.pedromonjo.com/2025/10/conversion-funnel-adobe-tools</id><content type="html" xml:base="https://www.pedromonjo.com/2025/10/conversion-funnel-adobe-tools.html"><![CDATA[<p>Today’s post is going to be different from my typical post where I explain how to solve for something or I explain my point of view. I will just list the Adobe tools based on a particular order, the order defined by the conversion funnel. It is just a reference, a mental map of tools, and very personal.</p>

<!--more-->

<style type="text/css">
  table.table td:first-child {
    font-weight: bold;
    /* width: 28%; */
  }
  @media screen and (min-width: 768px) {
    table#table-funnel {
      width: auto;
    }
    table#table-funnel td:first-child {
      width: auto;
    }
  }
</style>

<h2 id="conversion-funnel">Conversion Funnel</h2>

<p>I am sure you are all familiar with the typical sales or conversion funnel. There are many ways to show it, and it differs between business-to-business (B2B) and business-to-consumer (B2C). In the B2B world, Marketo will be at the center of most marketing activities.</p>

<p>In this post, though, I will explain a B2C case, as I am more familiar with it. This is a typical B2C conversion funnel:</p>

<p><img src="https://www.pedromonjo.com/assets/images/originals/2025/2025-10-05-conversion-funnel-adobe-tools/sales-funnel.svg" alt="Conversion Funnel" width="200" class="img-responsive" /></p>

<table id="table-funnel" class="table table-bordered">
  <thead>
    <tr>
      <th style="text-align: center">Initial</th>
      <th style="text-align: left">Full word</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">A</td>
      <td style="text-align: left">Awareness</td>
    </tr>
    <tr>
      <td style="text-align: center">I</td>
      <td style="text-align: left">Interest</td>
    </tr>
    <tr>
      <td style="text-align: center">D</td>
      <td style="text-align: left">Desire</td>
    </tr>
    <tr>
      <td style="text-align: center">A</td>
      <td style="text-align: left">Action</td>
    </tr>
  </tbody>
</table>

<p>And it is a funnel because, as you go from top to bottom, there are fewer people in each section.</p>

<p>Let’s go step by step and identify what Adobe tools I believe belong to each one.</p>

<h2 id="top-of-the-funnel-awareness">Top of the Funnel: Awareness</h2>

<p>At the top of the funnel we find the <em>awareness</em> state. I like the definition of this stage that I found on <a href="https://www.bol-agency.com/blog/awareness-consideration-conversion-marketing-funnel">this post</a>:</p>

<blockquote>
  <p>This is where potential customers first become aware of your brand or product. They might not be actively looking to buy anything yet, but you want to make them aware of your existence and pique their interest.</p>
</blockquote>

<p>Since you know nothing about them and they do not know your company, you have to fish them outside of your properties. Your options with Adobe tools are:</p>

<table class="table table-striped">
  <tbody>
    <tr>
      <td>Adobe Advertising</td>
      <td>This tool has had multiple names, but the idea is always the same: place paid ads on different platforms, such as search, media, and social. Remember, you need to tell the rest of the world that you exist.</td>
    </tr>
    <tr>
      <td>Adobe Audience Manager</td>
      <td>If you still have this tool and are ingesting third-party data, you can create segments of these cookies and share them with demand-side platforms.</td>
    </tr>
    <tr>
      <td>Adobe Campaign</td>
      <td>This is a bit of an edge case, but if you have the rights to use a list of email addresses of non-customers, you could send them an email. I would not use Adobe Journey Optimizer (AJO) for this.</td>
    </tr>
    <tr>
      <td>Adobe Experience Manager</td>
      <td>You may be wondering why I am including AEM here, when a potential customer has not even visited your website. The reason is that your marketing content needs to be attractive to search engines (SEO), <a href="https://www.pedromonjo.com/2025/09/from-seo-to-geo.html">and now generative AI too</a>, so that they promote your content.</td>
    </tr>
  </tbody>
</table>

<h2 id="middle-of-the-funnel-interest--desire">Middle of the Funnel: Interest &amp; Desire</h2>

<p>Now you have piqued the interest of a potential buyer. They have found you somewhere in the wild Internet and they have clicked a link that takes them to your website. Now it is your opportunity to show that you are their best option.</p>

<p>The difference between these two steps is subtle, and many combine them into one step. However, you could say that <em>interest</em> is just curiosity and research, while <em>desire</em> shows an appetite to buy your products or services.</p>

<p>At this stage, you can use these Adobe products:</p>

<table class="table table-striped">
  <tbody>
    <tr>
      <td>Adobe Experience Manager</td>
      <td>This is the sweet spot for AEM. Successful online businesses need a steady flow of new content to show visitors. This content needs to show exactly what the potential buyer is looking for. Such a large volume of content requires a tool that helps you manage it.</td>
    </tr>
    <tr>
      <td>Adobe Analytics<br />Customer Journey Analytics</td>
      <td>After landing on your website, you can start tracking your visitors to understand what they want, what they do not like, the issues they are having, and a bit about who they are.</td>
    </tr>
    <tr>
      <td>Adobe Target</td>
      <td>You should be running A/B tests on landing pages to see which version resonates the most, and personalization based on information that you have about the visitor: location, language, previous pages, traffic source...</td>
    </tr>
    <tr>
      <td>Real-Time Customer Data Platform</td>
      <td>The moment a new <a href="https://www.pedromonjo.com/2025/06/revisiting-ecid-service.html">ECID</a> is sent to the <a href="https://www.pedromonjo.com/category/platform/">Adobe Experience Platform</a> (AEP), a <a href="https://experienceleague.adobe.com/en/docs/experience-platform/profile/ui/prospect-profile">prospect profile</a> is created. You can use it to create segments.</td>
    </tr>
    <tr>
      <td>Adobe Campaign<br />Adobe Journey Optimizer</td>
      <td>In some cases, the prospect may provide you with an email address, for example, if they sign up for a newsletter. This is gold, and you can use it to send them tailored marketing material.</td>
    </tr>
  </tbody>
</table>

<h2 id="bottom-of-the-funnel-action">Bottom of the funnel: Action</h2>

<p>You will also see this stage named <em>conversion</em>. This is when the customer finally opens their wallet and buys your products or services. I know that there are some cases where the action is not necessarily paying for something, but the tools needed to manage it remain mostly the same.</p>

<p>This is my selection:</p>

<table class="table table-striped">
  <tbody>
    <tr>
      <td>Adobe Experience Manager</td>
      <td>You still need some web pages to manage the conversion.</td>
    </tr>
    <tr>
      <td>Adobe Commerce</td>
      <td>This is the sweet spot for this tool, where you manage the catalog, payment, shipping, and anything else related to the final sale.</td>
    </tr>
    <tr>
      <td>Adobe Analytics<br />Customer Journey Analytics</td>
      <td>Again, you want to track and measure what is happening, to identify trends or issues with the conversion. You want to make sure that the process is as frictionless as possible. Besides, you want to report on how much money you are making and attribute this revenue to the traffic source.</td>
    </tr>
    <tr>
      <td>Adobe Campaign<br />Adobe Journey Optimizer</td>
      <td>If the prospect drops out at the last minute, you should encourage them to come back, which is usually called a *cart abandonment* campaign. If the prospect converts and becomes a customer, you will need to send them a welcome email or updates related to the purchase.</td>
    </tr>
  </tbody>
</table>

<h2 id="retention">Retention</h2>

<p>So, someone has taken the action that you wanted. Congratulations!</p>

<p>However, is that the end? Absolutely not! Now you have to keep making this customer happy and, in the future, when they want to buy similar products or services again, they buy from you. And here you have an ace up your sleeve: you already know who they are and what they like. You can, and should, be using this information to your advantage.</p>

<p>All your Adobe tools are there to support you:</p>

<table class="table table-striped">
  <tbody>
    <tr>
      <td>Adobe Experience Manager</td>
      <td>Create new content that your customers will love.</td>
    </tr>
    <tr>
      <td>Adobe Commerce</td>
      <td>Be ready for any up-sell or cross-sell, and have offers available.</td>
    </tr>
    <tr>
      <td>Adobe Analytics<br />Customer Journey Analytics</td>
      <td>Understand what your customers want.</td>
    </tr>
    <tr>
      <td>Adobe Campaign<br />Adobe Journey Optimizer</td>
      <td>Keep your customer informed until the service or product is delivered. Then, make sure they know what you are doing and what new things you are working on.</td>
    </tr>
    <tr>
      <td>Adobe Target</td>
      <td>Personalize the content of the website or app, knowing what this customer likes or wants.</td>
    </tr>
  </tbody>
</table>

<p>And this is my list. What is yours?</p>

<p> </p>

<p><small><a href="https://www.pexels.com/photo/orange-and-gray-concrete-structures-749380/">Photo by Anton H</a></small></p>]]></content><author><name>Pedro Monjo</name></author><category term="Opinion" /><category term="MSA" /><summary type="html"><![CDATA[Today’s post is going to be different from my typical post where I explain how to solve for something or I explain my point of view. I will just list the Adobe tools based on a particular order, the order defined by the conversion funnel. It is just a reference, a mental map of tools, and very personal.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.pedromonjo.com/assets/images/originals/2025/2025-10-05-conversion-funnel-adobe-tools/funnel.jpg" /><media:content medium="image" url="https://www.pedromonjo.com/assets/images/originals/2025/2025-10-05-conversion-funnel-adobe-tools/funnel.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>