<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Jair Talks Salesforce — English</title>
    <description>Notes on Salesforce, AI and architecture.</description>
    <language>en</language>
    <link>https://jairzh.com/en/</link>
    <atom:link href="https://jairzh.com/en/feed.xml" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Sat, 03 Oct 2026 21:55:49 +0800</lastBuildDate>
    
    
    <item>
      <title>Dreamforce 2026: Five Takeaways on Salesforce MCP</title>
      <description>&lt;p&gt;I attended Dreamforce 2026 in person this year. Every year, Dreamforce offers small hands-on challenges called &lt;strong&gt;Mini Hacks&lt;/strong&gt; to introduce the technologies Salesforce is focusing on. This year there were five challenges: &lt;strong&gt;three involved MCP&lt;/strong&gt;, while the other two covered model selection and React.&lt;/p&gt;

&lt;p&gt;That mix of topics tells a story: &lt;strong&gt;Salesforce is bringing CRM capabilities into the AI tools people already use.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Rather than walk through the setup steps, I want to share five things that stood out to me as a developer working with Salesforce every day.&lt;/p&gt;

&lt;h3 id=&quot;1-a-new-interface-layer-aiforce&quot;&gt;1. A new interface layer: AIforce&lt;/h3&gt;

&lt;p&gt;According to this year’s keynote, Salesforce now has four layers:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://article.asset.jairzh.com/img/2026/10/scr-20261003-jvml-d83cf5.jpeg&quot; alt=&quot;The four Salesforce layers presented at Dreamforce 2026&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Interface layer:&lt;/strong&gt; AIforce. The initial entry points include Claudeforce in Claude, Slackforce in Slack, and Agentforce Coworker in Lightning.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Agent layer:&lt;/strong&gt; Agentforce.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Application and semantic layer:&lt;/strong&gt; Sales Cloud, Service Cloud, and other applications.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Data layer:&lt;/strong&gt; Data 360.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why add an interface layer? Employees spend their day across email, documents, presentations, and communication tools as well as CRM. More people are doing their work directly in AI tools. CRM needs to participate in that ecosystem; asking users to return to Salesforce every time they need an agent adds friction.&lt;/p&gt;

&lt;p&gt;The mechanism for bringing these capabilities into other tools is &lt;strong&gt;MCP (Model Context Protocol)&lt;/strong&gt;, a standard protocol connecting AI clients to external services. A service exposes its capabilities as tools. Each tool has a natural-language description that helps the AI decide which one to call.&lt;/p&gt;

&lt;p&gt;In Salesforce, MCP configuration lives in Setup. Standard servers provide CRUD and search tools. Custom servers can expose Flows, Apex, agents, and Prompt Templates as tools. Once configured, a server gives you a URL to add to an MCP-compatible client. The Tableau Next and Slack Mini Hacks follow the same pattern, with different sources of capabilities and different entry points.&lt;/p&gt;

&lt;h3 id=&quot;2-development-is-shifting-from-building-pages-to-designing-tools&quot;&gt;2. Development is shifting from building pages to designing tools&lt;/h3&gt;

&lt;p&gt;During the Mini Hack, I first tried using only the standard server. The AI could complete a multi-step booking workflow using CRUD, but it had to query and create records object by object. &lt;strong&gt;It was slow, and it had to work out the relationships between objects each time.&lt;/strong&gt; The results were inconsistent, too.&lt;/p&gt;

&lt;p&gt;When I wrapped the entire workflow in a Flow and exposed it as a single tool, one call completed the process.&lt;/p&gt;

&lt;p&gt;My takeaways:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Package frequently used core workflows into tools with the right scope.&lt;/strong&gt; Do not expect the AI to assemble them from individual CRUD operations every time.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The tool description is the new UI.&lt;/strong&gt; Previously, people interacted with buttons and forms. Now the model interprets tool names, descriptions, and parameter explanations. Their clarity directly affects whether it selects the right tool and passes the right inputs.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Standard servers cannot be modified, but their tools can be combined.&lt;/strong&gt; Create a custom server, use Add from Server to include the standard tools you need, then add your own Apex actions. This lets you assemble different servers for different roles.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;An agent has a brain, but its ability to act depends on the tools we give it.&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;3-plan-authentication-and-security-early&quot;&gt;3. Plan authentication and security early&lt;/h3&gt;

&lt;p&gt;Connections use an &lt;strong&gt;External Client App (ECA)&lt;/strong&gt;. A few configuration details are easy to miss:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Select the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mcp_api&lt;/code&gt; OAuth scope; the ordinary &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;api&lt;/code&gt; scope is not sufficient. Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;refresh_token&lt;/code&gt; as well.&lt;/li&gt;
  &lt;li&gt;Enable Require PKCE and Issue JWT-based access tokens for named users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bigger question is &lt;strong&gt;whose identity the tools run under&lt;/strong&gt;. When an external AI client calls your exposed Apex, it acts as the authorized user. Custom Apex actions therefore need to respect sharing rules and field permissions, using mechanisms such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;with sharing&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WITH USER_MODE&lt;/code&gt;. Treat parameters from the model as user input, and do not concatenate them directly into dynamic SOQL. In the MCP era, you can no longer assume that only an internal page will call your code.&lt;/p&gt;

&lt;p&gt;Salesforce is also introducing a permission configuration called &lt;strong&gt;Scoped Access&lt;/strong&gt;. It addresses the situation where an AI client acts on behalf of a user whose permissions are broader than the client needs. Scoped Access lets you adjust the permission scope for that context rather than simply relying on the user’s full set of permissions.&lt;/p&gt;

&lt;h3 id=&quot;4-cost-mcp-calls-will-consume-flex-credits&quot;&gt;4. Cost: MCP calls will consume Flex Credits&lt;/h3&gt;

&lt;p&gt;This is easy to overlook when designing a solution. A breakout session noted that &lt;strong&gt;successful MCP calls in production will consume Flex Credits&lt;/strong&gt;. According to published reports, each successful call will count as a “Headless Platform Interaction,” tracked in Digital Wallet. The exact credit rate has not yet been announced. The rollout is planned for around November, alongside agent registration and security controls, with 30 days’ notice before billing begins.&lt;/p&gt;

&lt;p&gt;This reinforces the point about tool design: &lt;strong&gt;well-scoped tools require fewer calls, which can reduce cost.&lt;/strong&gt; Having an AI assemble a workflow one CRUD operation at a time can be expensive as well as slow.&lt;/p&gt;

&lt;h3 id=&quot;5-more-choice-in-models-and-front-end-frameworks&quot;&gt;5. More choice in models and front-end frameworks&lt;/h3&gt;

&lt;p&gt;The other two Mini Hacks may seem unrelated to MCP, but they reflect the same direction: &lt;strong&gt;greater openness&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Models can be changed.&lt;/strong&gt; Prompt Templates and agents can use different models. In the new Builder, which uses Agent Script, each subagent can specify its own model through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;model_config&lt;/code&gt;. My approach: if prompt refinement stops improving inconsistent results, test another model. Start with a less expensive model, and reserve stronger models for the subagents handling complex tasks. Always rerun your test cases after changing models, because models differ in how they use tools. Agents built in the older Builder can move to the new version using the official Upgrade feature.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;React is an option for the front end.&lt;/strong&gt; Multi-Framework is now generally available, and React applications can be deployed to Salesforce as UI Bundles. When I asked “React or LWC?” at the event, the answer was direct: if your team knows React, use React. My view is that React fits complex, customer-facing single-page applications, while LWC remains the main choice for internal applications that need standard Lightning components.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;try-it-yourself&quot;&gt;Try it yourself&lt;/h3&gt;

&lt;p&gt;Trailhead now has modules covering AIforce. Search for them on Trailhead if you want to explore these capabilities hands-on.&lt;/p&gt;

&lt;h3 id=&quot;closing-thoughts&quot;&gt;Closing thoughts&lt;/h3&gt;

&lt;p&gt;My biggest takeaway from Dreamforce 2026 is that &lt;strong&gt;what Salesforce developers build for is changing&lt;/strong&gt;. We still build interfaces for people, and now we also design tools for AI. Those tools need the right scope, clear descriptions, carefully enforced permissions, and attention to AI costs.&lt;/p&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
  &lt;p&gt;Translated from my Chinese article, originally published on the SalesforceTalk WeChat account. &lt;a href=&quot;https://mp.weixin.qq.com/s/fECELCxZHQV_L_18pKWbCg&quot;&gt;Read the original on WeChat&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
      <pubDate>Sat, 03 Oct 2026 00:00:00 +0800</pubDate>
      <link>https://jairzh.com/en/blog/2026/10/03/dreamforce-2026-salesforce-mcp</link>
      <guid isPermaLink="true">https://jairzh.com/en/blog/2026/10/03/dreamforce-2026-salesforce-mcp</guid>
    </item>
    
    <item>
      <title>How Agentic AI Changes the Work of Salesforce Admins and Developers</title>
      <description>&lt;p&gt;Agentic AI changes what Salesforce administrators and developers build, and how they build it. My original article presented this idea as a Chinese infographic based on TDX 2026. This English text edition makes its main points available without requiring the reader to interpret the image.&lt;/p&gt;

&lt;p&gt;The shift is toward &lt;strong&gt;intent-driven development&lt;/strong&gt;: describe the outcome, let AI assist with planning and implementation, then evaluate the result against the business requirement.&lt;/p&gt;

&lt;h2 id=&quot;1-what-makes-an-agent-different&quot;&gt;1. What makes an agent different?&lt;/h2&gt;

&lt;p&gt;An agent combines interpreting an instruction with reasoning, planning, and taking actions through available tools. A request such as “send the report to the sales team” can involve several steps rather than one response.&lt;/p&gt;

&lt;p&gt;This is a useful design direction, not a guarantee that an agent always understands the request correctly. The outcome still depends on its instructions, tools, permissions, and the evidence supplied to it.&lt;/p&gt;

&lt;h2 id=&quot;2-why-it-matters-for-salesforce-work&quot;&gt;2. Why it matters for Salesforce work&lt;/h2&gt;

&lt;p&gt;AI can help interpret requirements, generate code and configuration, handle repetitive tasks, and connect work across systems. That changes the allocation of effort: less time on repetitive production of artifacts, more on architecture, design, evaluation, and improvement.&lt;/p&gt;

&lt;p&gt;The developer’s responsibility does not disappear when a model generates the code. Someone still has to decide whether the implementation fits the requirement and operates safely in the platform.&lt;/p&gt;

&lt;h2 id=&quot;3-from-writing-every-line-to-guiding-the-work&quot;&gt;3. From writing every line to guiding the work&lt;/h2&gt;

&lt;p&gt;The infographic describes a workflow with four parts:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Express the intended outcome.&lt;/li&gt;
  &lt;li&gt;Let an AI assistant reason about and generate an implementation.&lt;/li&gt;
  &lt;li&gt;Review the resulting artifact.&lt;/li&gt;
  &lt;li&gt;Correct and improve it through feedback.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The skills this requires include making intent clear, evaluating generated code, understanding architectural trade-offs, and shaping the behavior of an agent. Producing code is one part of a wider engineering process.&lt;/p&gt;

&lt;h2 id=&quot;4-openness-choice-and-trust&quot;&gt;4. Openness, choice, and trust&lt;/h2&gt;

&lt;p&gt;Another theme is using AI tools alongside the Salesforce platform rather than limiting all work to one interface. MCP is one way to connect a compatible AI client to capabilities exposed by a service.&lt;/p&gt;

&lt;p&gt;The original infographic contrasted external coding assistants with the platform’s own developer experience. The practical question is which environment fits the work, while maintaining the required controls around code, data, and access.&lt;/p&gt;

&lt;p&gt;For more detail on exposing useful capabilities through MCP, see my &lt;a href=&quot;/en/blog/2026/10/03/dreamforce-2026-salesforce-mcp&quot;&gt;Dreamforce 2026 MCP article&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;5-an-application-and-an-agent-can-work-together&quot;&gt;5. An application and an agent can work together&lt;/h2&gt;

&lt;p&gt;An application provides visible structure: components, forms, validation, and a familiar workflow. An agent provides a conversational way to interpret a request and coordinate several steps.&lt;/p&gt;

&lt;p&gt;These strengths are complementary. A conversation can guide the task, while a form or record view supplies a clear interface for entering details and reviewing the result. The best experience does not have to force every interaction into text.&lt;/p&gt;

&lt;h2 id=&quot;6-evaluate-behavior-not-only-artifacts&quot;&gt;6. Evaluate behavior, not only artifacts&lt;/h2&gt;

&lt;p&gt;Traditional application tests check defined behavior under particular inputs. Agent evaluation also needs to examine how the agent interprets varied requests, chooses tools, and handles uncertainty.&lt;/p&gt;

&lt;p&gt;The infographic highlights Testing Center and observability as parts of that process. The underlying practice is to test representative prompts, inspect failures, and improve the behavior with evidence.&lt;/p&gt;

&lt;p&gt;A successful demonstration is not enough. Check variations in wording, missing information, tool failures, and whether the agent asks for clarification at the right point.&lt;/p&gt;

&lt;h2 id=&quot;7-what-this-means-for-administrators&quot;&gt;7. What this means for administrators&lt;/h2&gt;

&lt;p&gt;For administrators, the opportunity is to use natural-language assistance with configuration and automation, and to spend more time understanding business problems.&lt;/p&gt;

&lt;p&gt;Flows, reports, pages, and formulas still need a coherent design. AI assistance is most useful when the administrator can explain what the business needs and verify that the resulting configuration satisfies it.&lt;/p&gt;

&lt;h2 id=&quot;8-the-skills-worth-developing&quot;&gt;8. The skills worth developing&lt;/h2&gt;

&lt;p&gt;The infographic brings together several capabilities:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Understanding the business and its users.&lt;/li&gt;
  &lt;li&gt;Expressing intent clearly.&lt;/li&gt;
  &lt;li&gt;Working with AI while judging the output critically.&lt;/li&gt;
  &lt;li&gt;Reviewing quality and applying governance.&lt;/li&gt;
  &lt;li&gt;Testing and observing agent behavior.&lt;/li&gt;
  &lt;li&gt;Continuing to learn as the tools change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These build on existing platform knowledge. Knowing the data model, security model, and transaction boundaries remains important when another system helps produce the implementation.&lt;/p&gt;

&lt;h2 id=&quot;9-learn-with-the-community&quot;&gt;9. Learn with the community&lt;/h2&gt;

&lt;p&gt;Hands-on experiments, hackathons, and shared Trailblazer examples help turn an abstract idea into practical experience. Study how others approach the same problem, then test the pattern against your own requirements.&lt;/p&gt;

&lt;p&gt;My main takeaway from the infographic is that administrators and developers increasingly act as designers and reviewers of AI-assisted work. Their business understanding and technical judgment become more important as more of the implementation can be generated.&lt;/p&gt;

&lt;p&gt;Source for the original infographic: &lt;a href=&quot;https://www.salesforce.com/plus/experience/tdx_2026/series/tdx_2026_highlights/episode/episode-s1e33&quot;&gt;TDX 2026 Highlights, S1E33&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
  &lt;p&gt;Originally published on 2026-05-25. English edition revised on October 3, 2026. Adapted the Chinese infographic into an English text article. &lt;a href=&quot;/blog/2026/05/25/agentic-era-salesforce-development&quot;&gt;Read the Chinese original&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
      <pubDate>Mon, 25 May 2026 20:00:00 +0800</pubDate>
      <link>https://jairzh.com/en/blog/2026/05/25/agentic-era-salesforce-development</link>
      <guid isPermaLink="true">https://jairzh.com/en/blog/2026/05/25/agentic-era-salesforce-development</guid>
    </item>
    
    <item>
      <title>Four Practical Patterns for Reactive Salesforce Screen Flows</title>
      <description>&lt;p&gt;Screen Flow reactivity lets components on the same screen respond to one another as the user works. A checkbox can disable an input, a formula can turn a subject into a suggested priority, and several inputs can contribute to a calculated date—all without asking the user to click Next.&lt;/p&gt;

&lt;p&gt;The useful pattern is to treat a screen as a small interactive application. Components supply values, formulas transform them, and other components use the results. Here are four examples from my original article.&lt;/p&gt;

&lt;h2 id=&quot;1-let-one-component-control-another&quot;&gt;1. Let one component control another&lt;/h2&gt;

&lt;p&gt;Add a Checkbox component called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Confirmation&lt;/code&gt; and a Text component called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Subject&lt;/code&gt;. Bind the Text component’s Disabled property to the checkbox’s Boolean value.&lt;/p&gt;

&lt;p&gt;When the user selects the checkbox, the subject becomes read-only. When they clear it, they can edit the subject again. This is useful for an explicit confirmation step before submission.&lt;/p&gt;

&lt;p&gt;The connection is direct: the checkbox is the source, and the Disabled property is the target. No formula is needed if the source already provides the right type.&lt;/p&gt;

&lt;h2 id=&quot;2-use-a-formula-as-a-routing-layer&quot;&gt;2. Use a formula as a routing layer&lt;/h2&gt;

&lt;p&gt;A formula becomes useful when the target needs a different value from the one the user entered. Suppose the subject should suggest a high priority when it contains “urgent.” A Text formula resource can express that rule:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;IF(
    CONTAINS(LOWER({!Subject}), &quot;urgent&quot;),
    &quot;High&quot;,
    &quot;Normal&quot;
)
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Reference the formula as the default value of a compatible priority component. Its choice values must match &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;High&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Normal&lt;/code&gt;; labels and stored values are not necessarily identical.&lt;/p&gt;

&lt;p&gt;This produces a suggested priority, not a complete business validation rule. Check how the component behaves after the user manually changes it, and decide whether that choice should take precedence over the suggestion.&lt;/p&gt;

&lt;h2 id=&quot;3-combine-inputs-to-calculate-a-date&quot;&gt;3. Combine inputs to calculate a date&lt;/h2&gt;

&lt;p&gt;The original example combines a priority-based number of days with a Slider adjustment. Configure the priority choice to provide a numeric value and create a Date formula such as:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;TODAY()
+ BLANKVALUE({!PriorityDays}, 0)
+ BLANKVALUE({!AdjustmentDays}, 0)
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Use the result as the default value for a compatible Date component. A user can change either input and see the proposed due date update on the same screen.&lt;/p&gt;

&lt;p&gt;Explicit defaults matter. The formula should behave sensibly before all inputs have values. Also, this example adds calendar days; a requirement for business days needs a different calculation.&lt;/p&gt;

&lt;h2 id=&quot;4-show-the-right-message-at-the-right-time&quot;&gt;4. Show the right message at the right time&lt;/h2&gt;

&lt;p&gt;Use conditional visibility to show an approval warning when the Slider exceeds a threshold. For example:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Show the warning when &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AdjustmentDays &amp;gt; 3&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Show the normal guidance when &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AdjustmentDays &amp;lt;= 3&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These conditions cover the boundary value of exactly three days. Otherwise, it is easy to accidentally create a state where neither message appears.&lt;/p&gt;

&lt;p&gt;The result is immediate guidance: the screen tells the user when their choice requires attention, rather than waiting until they submit it.&lt;/p&gt;

&lt;h2 id=&quot;what-to-check-before-shipping&quot;&gt;What to check before shipping&lt;/h2&gt;

&lt;p&gt;Reactivity is supported by particular components, properties, and formula functions—not by every resource in Flow Builder. Check the flow’s API version and test initial values, cleared values, user overrides, and returning to the screen. Salesforce lists the supported &lt;a href=&quot;https://help.salesforce.com/s/articleView?id=sf.flow_build_reactive_screen_flow_components.htm&amp;amp;language=en_US&amp;amp;type=5&quot;&gt;components&lt;/a&gt; and &lt;a href=&quot;https://help.salesforce.com/s/articleView?id=sf.flow_build_reactive_screen_flow_formula_operators.htm&amp;amp;language=en_US&quot;&gt;formula operators&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My takeaway is that formulas can serve as the small logic layer between screen components. A handful of clear connections can make a flow feel responsive without adding a separate screen for every decision.&lt;/p&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
  &lt;p&gt;Originally published on 2026-05-24. English edition revised on October 3, 2026. Expanded with illustrative formulas and implementation considerations. &lt;a href=&quot;/blog/2026/05/24/salesforce-reactive-screen-flow&quot;&gt;Read the Chinese original&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
      <pubDate>Sun, 24 May 2026 11:33:23 +0800</pubDate>
      <link>https://jairzh.com/en/blog/2026/05/24/salesforce-reactive-screen-flow</link>
      <guid isPermaLink="true">https://jairzh.com/en/blog/2026/05/24/salesforce-reactive-screen-flow</guid>
    </item>
    
    <item>
      <title>Salesforce Archive: Retention, Access, and Cost Considerations</title>
      <description>&lt;p&gt;As a Salesforce org grows, old records compete with current work for storage and attention. Closed cases, completed activities, and historical custom-object records may need to remain available even when users rarely open them.&lt;/p&gt;

&lt;p&gt;Deleting everything is often unsuitable, while keeping everything in the primary org can become expensive and cumbersome. That is the problem I wanted to explain in my original article about Salesforce Archive.&lt;/p&gt;

&lt;h2 id=&quot;separate-active-data-from-historical-data&quot;&gt;Separate active data from historical data&lt;/h2&gt;

&lt;p&gt;The architectural idea is to distinguish two kinds of records:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Active data&lt;/strong&gt; supports current business processes and frequent access.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Historical data&lt;/strong&gt; is retained for reference, analysis, or the organization’s retention requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An archive policy determines when a record moves from the first group to the second. A backup serves a different purpose: recovering data after an incident. An archive needs an intentional access and retention model for historical information.&lt;/p&gt;

&lt;h2 id=&quot;clarify-which-archive-product-you-are-discussing&quot;&gt;Clarify which Archive product you are discussing&lt;/h2&gt;

&lt;p&gt;There is an important terminology issue when updating an older article. Salesforce’s current &lt;a href=&quot;https://www.salesforce.com/platform/data-archiving-solutions/&quot;&gt;Archive product page&lt;/a&gt; describes automated policies, storage analysis, access to archived records, and restoration.&lt;/p&gt;

&lt;p&gt;Salesforce also documents &lt;a href=&quot;https://help.salesforce.com/s/articleView?id=archive_overview.htm&amp;amp;language=en_US&amp;amp;type=5&quot;&gt;Legacy Salesforce Archive&lt;/a&gt;, an external-storage solution with a different setup and access model. Do not assume that a reference to AWS, Azure, or Salesforce Connect applies equally to every offering called Archive.&lt;/p&gt;

&lt;p&gt;For an evaluation, identify the exact product, entitlement, and storage architecture before designing the solution or estimating costs.&lt;/p&gt;

&lt;h2 id=&quot;four-capabilities-to-evaluate&quot;&gt;Four capabilities to evaluate&lt;/h2&gt;

&lt;h3 id=&quot;1-automated-policies&quot;&gt;1. Automated policies&lt;/h3&gt;

&lt;p&gt;Start with a business rule rather than a storage target. For example: a record is closed, older than the agreed retention threshold for active data, and no longer required by an active process.&lt;/p&gt;

&lt;p&gt;Translate that rule into the product’s supported policy configuration. The expression “older than three years” is a business condition, not a ready-to-run SOQL expression. Also consider related records; removing a parent without preserving needed context can leave historical data difficult to interpret.&lt;/p&gt;

&lt;h3 id=&quot;2-access-from-the-users-workflow&quot;&gt;2. Access from the user’s workflow&lt;/h3&gt;

&lt;p&gt;Moving data out is only half of the problem. Users need a way to find and understand it afterward. Decide who may view archived data, where it appears, and when a record needs to be restored.&lt;/p&gt;

&lt;p&gt;“Accessible” should be tested against real tasks. Can a support representative find an old case? Can they see the related information needed to interpret it? Does a report that once used active records still satisfy its requirement?&lt;/p&gt;

&lt;h3 id=&quot;3-storage-and-operating-cost&quot;&gt;3. Storage and operating cost&lt;/h3&gt;

&lt;p&gt;The expected saving depends on the actual contract, data volume, growth, and access pattern. Include the archive subscription and the work needed to operate it, not just a comparison between storage unit prices.&lt;/p&gt;

&lt;p&gt;An external-storage design may also involve infrastructure and connector costs. Those belong in the estimate only when they are part of the selected architecture.&lt;/p&gt;

&lt;h3 id=&quot;4-retention-and-governance&quot;&gt;4. Retention and governance&lt;/h3&gt;

&lt;p&gt;Define how long information remains available, who controls the policy, and how exceptions are handled. Retention, restoration, deletion, and records subject to a hold need a consistent operating process.&lt;/p&gt;

&lt;p&gt;A configured retention period should implement your organization’s requirements; it is not a substitute for deciding what those requirements are.&lt;/p&gt;

&lt;h2 id=&quot;when-archiving-is-worth-evaluating&quot;&gt;When archiving is worth evaluating&lt;/h2&gt;

&lt;p&gt;Three useful signals from the original article remain relevant:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Storage is dominated by historical records that users rarely access.&lt;/li&gt;
  &lt;li&gt;Important queries or reports are becoming difficult to operate at the current volume.&lt;/li&gt;
  &lt;li&gt;The business needs historical access, but keeping every record active is costly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Investigate performance separately. A large record count does not by itself diagnose data skew or prove that archiving will fix a slow query. Access patterns, relationships, query design, and indexing still matter.&lt;/p&gt;

&lt;h2 id=&quot;pricing-use-an-actual-quote&quot;&gt;Pricing: use an actual quote&lt;/h2&gt;

&lt;p&gt;The original article included a community price estimate whose units did not match its annual calculation. That estimate is intentionally omitted here.&lt;/p&gt;

&lt;p&gt;Salesforce currently describes pricing as tailored to the customer’s archiving needs. Obtain a quote for the specific product and volume, then compare the full operating cost with your alternatives. A historical community number is not a reliable budget for a new project.&lt;/p&gt;

&lt;h2 id=&quot;my-recommendation&quot;&gt;My recommendation&lt;/h2&gt;

&lt;p&gt;Begin with a retention and access policy, then evaluate the implementation. Some orgs need an archive product; others may have a simpler requirement for export, retention, and controlled deletion.&lt;/p&gt;

&lt;p&gt;The goal is to keep the production org useful while preserving the historical information the business actually needs. That requires a clear access model as much as a place to store the data.&lt;/p&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
  &lt;p&gt;Originally published on 2026-01-31. English edition revised on October 3, 2026. Distinguished current and legacy Archive offerings and removed the inconsistent community price estimate. &lt;a href=&quot;/blog/2026/01/31/salesforce-archive&quot;&gt;Read the Chinese original&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
      <pubDate>Sat, 31 Jan 2026 18:12:53 +0800</pubDate>
      <link>https://jairzh.com/en/blog/2026/01/31/salesforce-archive</link>
      <guid isPermaLink="true">https://jairzh.com/en/blog/2026/01/31/salesforce-archive</guid>
    </item>
    
    <item>
      <title>Using AI to Build a Salesforce Learning Plan from Your Trailhead Profile</title>
      <description>&lt;p&gt;Making a learning plan at the start of the year is a familiar routine. As a Salesforce professional, I normally combine Trailhead’s learning paths with my own experience to choose the certifications worth pursuing.&lt;/p&gt;

&lt;p&gt;For my 2026 plan, I tried a different approach: using ChatGPT and Gemini as advisers to cross-check my choices.&lt;/p&gt;

&lt;h2 id=&quot;1-start-with-the-right-data&quot;&gt;1. Start with the right data&lt;/h2&gt;

&lt;p&gt;My first attempt was to ask Gemini for recommendations directly. It suggested certifications I had already earned. The missing ingredient was my actual learning history.&lt;/p&gt;

&lt;p&gt;My Trailhead profile already contained useful evidence: badges, superbadges, certifications, and the distribution of skills I had studied. However, giving Gemini the profile URL did not produce usable content in that attempt.&lt;/p&gt;

&lt;p&gt;In the workflow described in my original article, I used ChatGPT with the Atlas browser to extract the profile into Markdown. The instruction was:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;Extract the information on this page in detail as Markdown.
Keep all the details. Do not summarize.
Write the output in English.
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Manually copying the visible profile text works as an alternative. The important part is to check the resulting document for omissions; the model should be analyzing your history, not reconstructing it from guesses.&lt;/p&gt;

&lt;h2 id=&quot;2-give-the-analysis-a-reusable-reference&quot;&gt;2. Give the analysis a reusable reference&lt;/h2&gt;

&lt;p&gt;I saved the extracted Markdown in a Google Doc, including the badge and superbadge information and the skill distribution. In the setup I used, Gemini could access the document as a reference.&lt;/p&gt;

&lt;p&gt;That gave me a reusable snapshot rather than a long, one-off message. I could return to the same evidence when reviewing the plan later.&lt;/p&gt;

&lt;p&gt;My analysis prompt was:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;Using this Trailhead profile&apos;s learning history and skill distribution,
and considering current developments in the Salesforce ecosystem,
recommend the two certifications most worth pursuing in 2026.
Explain the reasons for each recommendation.
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For another person’s plan, I would also include their role, years of experience, the work they expect to do, and their available study time. These details help distinguish a relevant next step from a generic list of popular certifications.&lt;/p&gt;

&lt;h2 id=&quot;3-compare-the-reasoning-with-your-own-judgment&quot;&gt;3. Compare the reasoning with your own judgment&lt;/h2&gt;

&lt;p&gt;The result surprised me. Gemini recommended exactly the same two certifications I had already selected manually. More interestingly, its reasoning matched my own: it identified gaps in my existing skills and explained why those areas were appropriate next steps.&lt;/p&gt;

&lt;p&gt;The useful result was the method: give the model a verified learning history, then compare its recommendations and reasoning against your own goals.&lt;/p&gt;

&lt;p&gt;Agreement is not proof that a plan is optimal. Before enrolling in an exam, check the current credential information, prerequisites, and exam guide on &lt;a href=&quot;https://trailhead.salesforce.com/credentials&quot;&gt;Trailhead Credentials&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;a-prompt-for-the-wider-plan&quot;&gt;A prompt for the wider plan&lt;/h2&gt;

&lt;p&gt;If you want to work on goals beyond certification, this is a useful starting point:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;Write my goals for the coming year.
Ask me the most important questions to craft the best possible set of goals.
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The key lesson from this experiment was simple: &lt;strong&gt;better evidence produces more relevant advice&lt;/strong&gt;. If the result feels generic, improve the input with your work history, responsibilities, and intended direction before asking for another recommendation.&lt;/p&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
  &lt;p&gt;Originally published on 2026-01-18. English edition revised on October 3, 2026. Translated the prompts and clarified that the browser and document workflow describes the original experiment. &lt;a href=&quot;/blog/2026/01/18/ai-assisted-salesforce-learning-plan-2026&quot;&gt;Read the Chinese original&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
      <pubDate>Sun, 18 Jan 2026 12:56:57 +0800</pubDate>
      <link>https://jairzh.com/en/blog/2026/01/18/ai-assisted-salesforce-learning-plan-2026</link>
      <guid isPermaLink="true">https://jairzh.com/en/blog/2026/01/18/ai-assisted-salesforce-learning-plan-2026</guid>
    </item>
    
    <item>
      <title>Using Platform Events to Offload Complex Apex Trigger Work</title>
      <description>&lt;p&gt;An Apex trigger often starts with a small responsibility and gradually accumulates expensive work: calculations, integrations, and updates across several objects. Eventually, that work needs a transaction of its own.&lt;/p&gt;

&lt;p&gt;My original article explored using a Platform Event to wake an asynchronous worker, while keeping the actual work in persistent Salesforce records. The separation is still useful, but the event should be a notification rather than the only copy of the business request.&lt;/p&gt;

&lt;h2 id=&quot;why-simply-enqueueing-more-jobs-can-fail&quot;&gt;Why simply enqueueing more jobs can fail&lt;/h2&gt;

&lt;p&gt;A straightforward response to slow trigger work is to move it into a future method or a Queueable job. That can be appropriate, but the calling context matters. Synchronous and asynchronous transactions have different enqueue limits, and several triggers in one transaction may share the same budget.&lt;/p&gt;

&lt;p&gt;Even record counts can be misleading. Two objects processed in separate trigger batches can produce more enqueue attempts than a single combined record count suggests. Repeated DML and recursive updates add further complexity.&lt;/p&gt;

&lt;p&gt;Also, moving work into a subscriber does not make every operation legal there. If an Apex Platform Event trigger needs to initiate an HTTP integration, design the handoff to a callout-capable asynchronous job rather than assuming the event trigger can make the callout directly.&lt;/p&gt;

&lt;h2 id=&quot;keep-the-work-in-records-use-the-event-to-signal-it&quot;&gt;Keep the work in records; use the event to signal it&lt;/h2&gt;

&lt;p&gt;The pattern has three parts:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Mark or store the pending work.&lt;/strong&gt; Update a status on the business record, or create a separate work-request record containing the information the worker needs.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Publish a notification.&lt;/strong&gt; The event tells the subscriber that work is available.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Process and acknowledge it.&lt;/strong&gt; The subscriber queries the durable work, handles an appropriate batch, and records successful completion.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This means a notification does not have to carry every business field. It can be a wake-up signal. Pending work remains discoverable even if a notification is missed or processing stops unexpectedly.&lt;/p&gt;

&lt;p&gt;When the subscriber depends on records written by the publishing transaction, configure &lt;strong&gt;Publish After Commit&lt;/strong&gt;. Otherwise, a subscriber may act before the corresponding data is committed. Salesforce describes the distinction in &lt;a href=&quot;https://trailhead.salesforce.com/content/learn/modules/platform_events_basics/platform_events_define_publish&quot;&gt;Platform Event publishing behavior&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;make-pending-work-unambiguous&quot;&gt;Make pending work unambiguous&lt;/h2&gt;

&lt;p&gt;The original design used two timestamps and a formula to identify records needing processing. A useful interpretation is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RequestedAt&lt;/code&gt; records the latest requested change.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ProcessedAt&lt;/code&gt; records the request that was successfully handled.&lt;/li&gt;
  &lt;li&gt;A record is pending when it has a request and either has never been processed or has a newer request than the processed one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The worker must acknowledge the version it actually handled. If another transaction requests new work during processing, setting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ProcessedAt&lt;/code&gt; to an unrelated “now” could accidentally clear that newer request. A separate request record or explicit version number can make this easier to reason about.&lt;/p&gt;

&lt;p&gt;Timestamp fields are a tracking mechanism, not a locking mechanism. Concurrent workers still need a deliberate ownership or concurrency strategy.&lt;/p&gt;

&lt;h2 id=&quot;plan-for-retries-and-recovery&quot;&gt;Plan for retries and recovery&lt;/h2&gt;

&lt;p&gt;Asynchronous processing needs more than a happy path:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Make repeated processing safe. For an external operation, use an idempotency key that represents the business request.&lt;/li&gt;
  &lt;li&gt;Record success only after the required work is confirmed.&lt;/li&gt;
  &lt;li&gt;Preserve failure information and retry eligible work deliberately.&lt;/li&gt;
  &lt;li&gt;Monitor the age and volume of the backlog.&lt;/li&gt;
  &lt;li&gt;Provide a recovery process that can find pending records independently of the event stream.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this design, a periodic recovery scan can coexist with event-driven processing. The event provides prompt execution; the durable backlog provides a way to catch up. Accepting a missed notification should not mean accepting lost business work.&lt;/p&gt;

&lt;h2 id=&quot;update-the-capacity-assumptions&quot;&gt;Update the capacity assumptions&lt;/h2&gt;

&lt;p&gt;My 2023 article described a single processing stream. That should not be interpreted as an absolute limit for every current implementation. Salesforce documents &lt;strong&gt;parallel subscriptions&lt;/strong&gt; for supported custom high-volume events, with work distributed by a partition key. Ordering and concurrent updates must be considered when selecting that key. See &lt;a href=&quot;https://developer.salesforce.com/docs/platform/platform-events/guide/platform-events-ps.html&quot;&gt;Parallel Subscriptions for Apex Triggers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Capacity also depends on event allocations, subscriber configuration, batch size, processing time, and downstream limits. Publishing success is not the same as completed business processing. Check the current &lt;a href=&quot;https://developer.salesforce.com/docs/platform/platform-events/guide/platform-events-api-considerations.html&quot;&gt;publishing and subscribing considerations&lt;/a&gt; for your org and event type instead of treating an old hourly figure as a universal budget.&lt;/p&gt;

&lt;h2 id=&quot;when-i-would-use-it&quot;&gt;When I would use it&lt;/h2&gt;

&lt;p&gt;Use a Queueable directly when the workflow is simple and the transaction’s enqueue budget is well understood. Consider a durable backlog plus Platform Event notification when several triggers need to hand off work, when the producer and consumer should be separated, or when independent recovery is important.&lt;/p&gt;

&lt;p&gt;The valuable idea is not that Platform Events remove limits. It is that they let the trigger signal work while another transaction handles it, with the business state stored somewhere recoverable.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/sirephil/sf-async-from-trigger&quot;&gt;original demonstration repository&lt;/a&gt; provides background for the pattern; adapt any sample to your current requirements and platform version.&lt;/p&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
  &lt;p&gt;Originally published on 2023-08-27. English edition revised on October 3, 2026. Revised capacity assumptions and added durable backlog, concurrency, callout, and recovery considerations. &lt;a href=&quot;/blog/2023/08/27/platform-event-for-complex-trigger-processing&quot;&gt;Read the Chinese original&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
      <pubDate>Sun, 27 Aug 2023 18:03:37 +0800</pubDate>
      <link>https://jairzh.com/en/blog/2023/08/27/platform-event-for-complex-trigger-processing</link>
      <guid isPermaLink="true">https://jairzh.com/en/blog/2023/08/27/platform-event-for-complex-trigger-processing</guid>
    </item>
    
    <item>
      <title>A Practical Guide to Debugging Lightning Web Components</title>
      <description>&lt;p&gt;Lightning Web Components run as JavaScript in the browser, so Chrome DevTools is an essential part of debugging them. Salesforce adds a few complications: code can be minified, framework files can interrupt stepping, and values can appear through proxies.&lt;/p&gt;

&lt;p&gt;The workflow from my original article still has three useful parts: enable readable code, set breakpoints in your component, and configure DevTools so that it shows the information you need.&lt;/p&gt;

&lt;h2 id=&quot;enable-debug-mode-for-the-right-user&quot;&gt;Enable Debug Mode for the right user&lt;/h2&gt;

&lt;p&gt;In Salesforce Setup, search for &lt;strong&gt;Debug Mode&lt;/strong&gt; and open &lt;strong&gt;Debug Mode Users&lt;/strong&gt;. Select the user doing the investigation and click Enable. Reload the page containing the component.&lt;/p&gt;

&lt;p&gt;Debug Mode makes the component code easier to inspect. It is a troubleshooting setting, so enable it for the users who need it rather than assuming every user’s session should use the same configuration. Salesforce explains the behavior in &lt;a href=&quot;https://developer.salesforce.com/docs/platform/lwc/guide/debug-debug-mode.html&quot;&gt;Debug Mode&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;find-your-component-and-set-a-breakpoint&quot;&gt;Find your component and set a breakpoint&lt;/h2&gt;

&lt;p&gt;Open Chrome DevTools with the browser menu or a keyboard shortcut, then select &lt;strong&gt;Sources&lt;/strong&gt;. Use the source tree or Open File to locate the JavaScript file for the custom component.&lt;/p&gt;

&lt;p&gt;Click a line number to set a breakpoint. Reload the page or repeat the interaction that triggers the code. When execution pauses, inspect local variables, the call stack, and the values entering your handler.&lt;/p&gt;

&lt;p&gt;A breakpoint placed after the initial render will not tell you what happened before that point. For initialization problems, reload with the breakpoint already set. For an interaction problem, repeat the actual action rather than only refreshing the page.&lt;/p&gt;

&lt;h2 id=&quot;ignore-framework-code-deliberately&quot;&gt;Ignore framework code deliberately&lt;/h2&gt;

&lt;p&gt;My 2019 article used Chrome’s older term, &lt;strong&gt;Blackboxing&lt;/strong&gt;. The current UI calls the feature &lt;strong&gt;Ignore List&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In DevTools Settings, open Ignore List and add patterns for framework or library files that are distracting you. You can also use a source file’s context menu to ignore that particular script.&lt;/p&gt;

&lt;p&gt;Be careful with broad patterns. A rule that excludes a large component directory can hide code you are trying to inspect. Confirm that your own component remains visible and available for stepping, and remove the rule when investigating a framework boundary.&lt;/p&gt;

&lt;p&gt;The goal is to make stepping useful: you want to follow the behavior you own, while still being able to inspect the surrounding code when it matters.&lt;/p&gt;

&lt;h2 id=&quot;enable-custom-formatters&quot;&gt;Enable custom formatters&lt;/h2&gt;

&lt;p&gt;In DevTools Settings, open Preferences and enable &lt;strong&gt;custom formatters&lt;/strong&gt;. This can make proxied values easier to read when debugging in Salesforce.&lt;/p&gt;

&lt;p&gt;Inspect the value before and after the line that changes it. A console entry by itself can be ambiguous when an object is inspected later; pausing at the relevant line gives you a clearer picture of the code’s state.&lt;/p&gt;

&lt;p&gt;Salesforce’s &lt;a href=&quot;https://trailhead.salesforce.com/content/learn/modules/lwc-troubleshooting/get-ready-to-troubleshoot&quot;&gt;JavaScript troubleshooting unit&lt;/a&gt; covers source-file discovery, Ignore List, and custom formatters.&lt;/p&gt;

&lt;h2 id=&quot;account-for-lightning-web-security&quot;&gt;Account for Lightning Web Security&lt;/h2&gt;

&lt;p&gt;The browser environment matters. When Lightning Web Security is enabled, some debugging behavior differs from older screenshots or examples. A console message’s apparent source is not always the component file you expected.&lt;/p&gt;

&lt;p&gt;Use Salesforce’s &lt;a href=&quot;https://developer.salesforce.com/docs/platform/lightning-components-security/guide/lws-debug.html&quot;&gt;Debug with LWS Enabled&lt;/a&gt; guidance alongside the ordinary LWC debugging workflow. Do not infer the origin of an error solely from a framework filename in the console.&lt;/p&gt;

&lt;h2 id=&quot;a-repeatable-investigation&quot;&gt;A repeatable investigation&lt;/h2&gt;

&lt;p&gt;For a practical debugging session, I would follow this order:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Reproduce the problem with the relevant user and record.&lt;/li&gt;
  &lt;li&gt;Enable Debug Mode for that user and reload.&lt;/li&gt;
  &lt;li&gt;Locate the custom component’s source file.&lt;/li&gt;
  &lt;li&gt;Set a breakpoint in the handler or lifecycle method involved.&lt;/li&gt;
  &lt;li&gt;Inspect inputs, state, and the call stack as the problem happens.&lt;/li&gt;
  &lt;li&gt;Use Ignore List and custom formatters to make the session readable.&lt;/li&gt;
  &lt;li&gt;Recheck the fix under the ordinary runtime settings.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The useful habit is to connect the visible symptom to a specific value and code path. Debug Mode and DevTools help you do that much faster than adding more console messages at random.&lt;/p&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
  &lt;p&gt;Originally published on 2019-03-09. English edition revised on October 3, 2026. Updated Blackboxing terminology to Ignore List and added Lightning Web Security considerations. &lt;a href=&quot;/blog/2019/03/09/debug-lightning-web-components&quot;&gt;Read the Chinese original&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
      <pubDate>Sat, 09 Mar 2019 08:25:29 +0800</pubDate>
      <link>https://jairzh.com/en/blog/2019/03/09/debug-lightning-web-components</link>
      <guid isPermaLink="true">https://jairzh.com/en/blog/2019/03/09/debug-lightning-web-components</guid>
    </item>
    
    <item>
      <title>Apex Tests That Do More Than Meet Code Coverage Requirements</title>
      <description>&lt;p&gt;Apex tests are part of deploying to Salesforce, but their value should extend well beyond the coverage requirement. A test that executes a line without checking its result can satisfy a metric while missing the very defect it was supposed to catch.&lt;/p&gt;

&lt;p&gt;My original article organized useful tests around four goals, five practices, and six challenges. That structure still provides a practical checklist.&lt;/p&gt;

&lt;h2 id=&quot;four-goals&quot;&gt;Four goals&lt;/h2&gt;

&lt;h3 id=&quot;1-test-positive-behavior&quot;&gt;1. Test positive behavior&lt;/h3&gt;

&lt;p&gt;Start with the intended business outcome. Create valid inputs, execute the operation, and check that it completes as expected. If a method has branches, cover the meaningful successful paths rather than only the easiest route through it.&lt;/p&gt;

&lt;h3 id=&quot;2-verify-the-end-state&quot;&gt;2. Verify the end state&lt;/h3&gt;

&lt;p&gt;Assertions are the part that makes a test useful. Check the returned value, the saved record, or the observable side effect that the requirement calls for.&lt;/p&gt;

&lt;div class=&quot;language-apex highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;n&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;assertEquals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Closed&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;actualStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;The operation should close the request&apos;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The assertion message should explain the expected behavior. A failure is much easier to investigate when the test tells you which promise was broken.&lt;/p&gt;

&lt;h3 id=&quot;3-test-negative-behavior&quot;&gt;3. Test negative behavior&lt;/h3&gt;

&lt;p&gt;Try inputs that should be rejected or handled differently. A blank required value, an unsupported transition, or an unavailable related record can be just as important as the happy path.&lt;/p&gt;

&lt;p&gt;Check the intended response: an error, a validation message, or an unchanged record. Merely catching an exception without asserting anything can hide a defect.&lt;/p&gt;

&lt;h3 id=&quot;4-test-bulk-behavior&quot;&gt;4. Test bulk behavior&lt;/h3&gt;

&lt;p&gt;Code that works for one record may fail when a batch arrives through an integration or data import. Test a representative collection—200 records is a useful trigger-batch scenario—and assert that all relevant records were processed correctly.&lt;/p&gt;

&lt;p&gt;That helps expose SOQL or DML inside loops. It is not a guarantee that every governor-limit scenario is covered: multiple objects, repeated updates, trigger recursion, and asynchronous callers can produce different behavior.&lt;/p&gt;

&lt;h2 id=&quot;five-practices&quot;&gt;Five practices&lt;/h2&gt;

&lt;h3 id=&quot;1-own-your-test-data&quot;&gt;1. Own your test data&lt;/h3&gt;

&lt;p&gt;Avoid relying on existing organization data through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SeeAllData=true&lt;/code&gt;. Such dependencies can make the same test pass in one org and fail in another.&lt;/p&gt;

&lt;p&gt;Create the data the test needs. Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@testSetup&lt;/code&gt; for shared setup where appropriate, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Test.loadData&lt;/code&gt; with a CSV static resource when fixtures are easier to express that way.&lt;/p&gt;

&lt;h3 id=&quot;2-centralize-repeated-setup&quot;&gt;2. Centralize repeated setup&lt;/h3&gt;

&lt;p&gt;A test-data factory keeps common record creation in one place. If a new required field is introduced, a factory is easier to update than dozens of nearly identical setup blocks.&lt;/p&gt;

&lt;p&gt;Keep scenario-specific choices visible in the test. A factory should reduce duplication without making the reader guess which conditions are being tested.&lt;/p&gt;

&lt;h3 id=&quot;3-use-descriptive-names&quot;&gt;3. Use descriptive names&lt;/h3&gt;

&lt;p&gt;A class name such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MyServiceTest&lt;/code&gt; makes the relationship to the production class clear. Method names should describe behavior: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rejectsMissingAccount&lt;/code&gt; is more informative than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;testMethod2&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;4-separate-setup-execution-and-verification&quot;&gt;4. Separate setup, execution, and verification&lt;/h3&gt;

&lt;p&gt;Create fixtures before &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Test.startTest()&lt;/code&gt;, execute the operation between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;startTest()&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stopTest()&lt;/code&gt;, then assert the result afterward.&lt;/p&gt;

&lt;div class=&quot;language-apex highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;// Arrange: create the records and inputs for this scenario.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Test&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;startTest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Act: invoke the operation under test.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Test&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;stopTest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Assert: verify the resulting records or return value.&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This makes the test boundary easy to understand and is important when testing supported asynchronous work.&lt;/p&gt;

&lt;h3 id=&quot;5-give-each-test-a-clear-purpose&quot;&gt;5. Give each test a clear purpose&lt;/h3&gt;

&lt;p&gt;One method should not try to exercise the entire application. Separate scenarios so that a failing test points to a specific behavior. Shared setup is useful; shared ambiguity is not.&lt;/p&gt;

&lt;h2 id=&quot;six-common-challenges&quot;&gt;Six common challenges&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Scenario&lt;/th&gt;
      &lt;th&gt;Useful approach&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Visualforce controllers&lt;/td&gt;
      &lt;td&gt;Set a page with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Test.setCurrentPage()&lt;/code&gt;, provide URL parameters, instantiate the controller, and assert navigation or state.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;SOSL&lt;/td&gt;
      &lt;td&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Test.setFixedSearchResults()&lt;/code&gt; to control the records returned by the test.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Asynchronous Apex&lt;/td&gt;
      &lt;td&gt;Use the supported &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;startTest()&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stopTest()&lt;/code&gt; pattern and assert the resulting work. Do not assume an arbitrary chain of nested jobs all runs in the test.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;HTTP callouts&lt;/td&gt;
      &lt;td&gt;Implement &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HttpCalloutMock&lt;/code&gt; and register it with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Test.setMock()&lt;/code&gt;; test success and failure responses.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;User access&lt;/td&gt;
      &lt;td&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.runAs()&lt;/code&gt; for user and sharing scenarios, then explicitly test the security behavior implemented by your production code.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Private members&lt;/td&gt;
      &lt;td&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@TestVisible&lt;/code&gt; when necessary, while favoring observable public behavior when that is sufficient.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;User context, record sharing, object access, and field access are different concerns. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runAs()&lt;/code&gt; alone should not be treated as proof that CRUD and field permissions are enforced. Queries and DML need the appropriate security behavior, such as user-mode operations, when the requirement calls for it. Salesforce explains these distinctions in &lt;a href=&quot;https://developer.salesforce.com/docs/platform/lwc/guide/apex-security&quot;&gt;Secure Apex Classes&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;a-test-should-protect-a-promise&quot;&gt;A test should protect a promise&lt;/h2&gt;

&lt;p&gt;Coverage answers whether code was executed. Useful tests answer whether the code kept its promise, rejected the wrong inputs, and behaved correctly under realistic workloads. Those are the tests that make the next change safer.&lt;/p&gt;

&lt;p&gt;For the platform-specific tools mentioned here, see the &lt;a href=&quot;https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/&quot;&gt;Apex Developer Guide&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
  &lt;p&gt;Originally published on 2016-05-22. English edition revised on October 3, 2026. Updated terminology and clarified bulk testing, asynchronous execution, and permission-testing boundaries. &lt;a href=&quot;/blog/2016/05/22/how-to-write-good-apex-test-methods&quot;&gt;Read the Chinese original&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
      <pubDate>Sun, 22 May 2016 15:25:29 +0800</pubDate>
      <link>https://jairzh.com/en/blog/2016/05/22/how-to-write-good-apex-test-methods</link>
      <guid isPermaLink="true">https://jairzh.com/en/blog/2016/05/22/how-to-write-good-apex-test-methods</guid>
    </item>
    
    <item>
      <title>Why Images Fail in Salesforce Emails—and How to Fix Them</title>
      <description>&lt;p&gt;An email template can look correct inside Salesforce and still show a broken image to its recipient. The usual reason is that the image URL works for a signed-in user but is not a usable image source for an external email client.&lt;/p&gt;

&lt;p&gt;My original article explained this through Salesforce Classic templates and Documents. The principle remains useful: &lt;strong&gt;an externally hosted image needs a stable, accessible URL&lt;/strong&gt;, and the actual delivered message needs to be tested.&lt;/p&gt;

&lt;h2 id=&quot;choose-the-method-for-the-template-you-use&quot;&gt;Choose the method for the template you use&lt;/h2&gt;

&lt;p&gt;Lightning templates and enhanced letterheads have image capabilities that differ from older Classic HTML and Visualforce templates. Start with the image tools in the editor for the template type you are using.&lt;/p&gt;

&lt;p&gt;Salesforce documents that saving a template can convert Salesforce content-document links into content-asset links. That is one reason manually copying a file preview URL is not equivalent to inserting an image through the supported editor. See &lt;a href=&quot;https://help.salesforce.com/s/articleView?id=sales.email_images.htm&amp;amp;language=en_US&amp;amp;type=5&quot;&gt;Using Images in Emails, Email Templates, and Enhanced Letterheads&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For a custom HTML template, you can reference a suitable hosted image explicitly:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;img&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://cdn.example.com/email/logo.png&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;alt=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Company logo&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;width=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;240&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;style=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;display:block; max-width:100%; height:auto;&quot;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Replace the example URL with your actual image. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src&lt;/code&gt; should identify the image response, not a page that previews a file.&lt;/p&gt;

&lt;h2 id=&quot;check-access-without-your-salesforce-session&quot;&gt;Check access without your Salesforce session&lt;/h2&gt;

&lt;p&gt;Open the final image URL in a browser session that is not signed in to your org. If it opens a login page or a file preview, it is not ready for the external recipient.&lt;/p&gt;

&lt;p&gt;This check is necessary, but it is not the entire email test. A recipient’s client may block remote images until the user enables them, and different clients render HTML differently. Provide useful alternative text and keep the message understandable when images are hidden.&lt;/p&gt;

&lt;p&gt;Only use public hosting for content intended to be publicly retrievable, such as a marketing logo. A picture’s presence in an internal Salesforce record does not establish that it is suitable for public distribution.&lt;/p&gt;

&lt;h2 id=&quot;the-historical-documents-approach&quot;&gt;The historical Documents approach&lt;/h2&gt;

&lt;p&gt;In the Classic workflow from the original article, the steps were:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Upload the image to a Documents folder.&lt;/li&gt;
  &lt;li&gt;Enable &lt;strong&gt;Externally Available Image&lt;/strong&gt; for the document.&lt;/li&gt;
  &lt;li&gt;Obtain its image URL.&lt;/li&gt;
  &lt;li&gt;Reference that URL in the HTML or Visualforce template.&lt;/li&gt;
  &lt;li&gt;Verify it without an authenticated org session.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The resulting URL used a form like this:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;https://&amp;lt;salesforce-host&amp;gt;/servlet/servlet.ImageServer?id=&amp;lt;document-id&amp;gt;&amp;amp;oid=&amp;lt;org-id&amp;gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is background for an existing Classic implementation, not a universal URL recipe for Lightning Files. Do not manufacture a download URL by replacing parts of a file preview link. Follow the supported image method for the particular storage and template type.&lt;/p&gt;

&lt;p&gt;Salesforce’s &lt;a href=&quot;https://help.salesforce.com/s/articleView?id=sf.email_template_images.htm&amp;amp;language=en_US&amp;amp;type=5&quot;&gt;Classic template image instructions&lt;/a&gt; describe that workflow and external image URLs.&lt;/p&gt;

&lt;h2 id=&quot;test-a-delivered-message&quot;&gt;Test a delivered message&lt;/h2&gt;

&lt;p&gt;After saving the template, send it to an external mailbox and inspect the result. A useful test covers:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The image loads when remote images are allowed.&lt;/li&gt;
  &lt;li&gt;The content remains readable when they are blocked.&lt;/li&gt;
  &lt;li&gt;The image has an appropriate size on desktop and mobile.&lt;/li&gt;
  &lt;li&gt;The URL does not depend on your login or an unexpectedly short expiration.&lt;/li&gt;
  &lt;li&gt;The received HTML references the intended image.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If it fails, inspect the image URL and response first. Authentication redirects, an expired link, or a file-preview response are different problems from an email client hiding remote content.&lt;/p&gt;

&lt;p&gt;The practical lesson is to validate the recipient’s experience, not just the template editor. A stable source and a real delivery test will find problems that an internal preview cannot.&lt;/p&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
  &lt;p&gt;Originally published on 2016-05-16. English edition revised on October 3, 2026. Updated guidance for Lightning templates and retained the Documents approach as historical Classic context. &lt;a href=&quot;/blog/2016/05/16/adding-image-in-email-template&quot;&gt;Read the Chinese original&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
      <pubDate>Mon, 16 May 2016 09:25:29 +0800</pubDate>
      <link>https://jairzh.com/en/blog/2016/05/16/adding-image-in-email-template</link>
      <guid isPermaLink="true">https://jairzh.com/en/blog/2016/05/16/adding-image-in-email-template</guid>
    </item>
    
    <item>
      <title>Salesforce 15- vs. 18-Character IDs: Avoiding Integration Bugs</title>
      <description>&lt;p&gt;A Salesforce record ID identifies one record. It is used in queries, exports, integrations, and URLs, so a small misunderstanding about its format can turn into a difficult data-matching bug.&lt;/p&gt;

&lt;p&gt;Salesforce IDs have two familiar forms: 15 characters and 18 characters. Both refer to the same underlying record, but they behave differently when a system compares text without respecting case.&lt;/p&gt;

&lt;h2 id=&quot;why-there-are-two-formats&quot;&gt;Why there are two formats&lt;/h2&gt;

&lt;p&gt;A 15-character ID is case-sensitive. The letters in these two example strings differ:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;00Qi000000g4YE2
00Qi000000g4ye2
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A case-insensitive comparison treats them as the same text even though the original 15-character values are different. That is a problem when joining Salesforce exports in spreadsheets or another system with case-insensitive keys.&lt;/p&gt;

&lt;p&gt;The 18-character form adds three characters encoding the capitalization of the first 15. That makes it suitable for matching in case-insensitive systems. Salesforce calls this form &lt;strong&gt;case-safe&lt;/strong&gt;. &lt;a href=&quot;https://help.salesforce.com/s/articleView?id=How-to-convert-a-15-character-id-to-a-18-character-id-1327109385626&amp;amp;language=en_US&amp;amp;type=1&quot;&gt;Salesforce’s conversion guidance&lt;/a&gt; describes both formats.&lt;/p&gt;

&lt;p&gt;Case-safe does not mean “change the capitalization whenever you like.” Preserve IDs exactly as received, and do not lowercase them before generating the suffix. If the original capitalization has already been lost, the conversion cannot infer it from a 15-character string alone.&lt;/p&gt;

&lt;h2 id=&quot;what-the-prefix-tells-you&quot;&gt;What the prefix tells you&lt;/h2&gt;

&lt;p&gt;The first three characters are the object’s key prefix. For example, a familiar prefix can help identify the object type of a record you are investigating.&lt;/p&gt;

&lt;p&gt;Avoid building business logic around assumptions about the rest of the ID, or hard-coding custom-object prefixes across organizations. In Apex, use the ID’s object type when you need it:&lt;/p&gt;

&lt;div class=&quot;language-apex highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;n&quot;&gt;Id&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;recordId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;001000000000001&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Schema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;SObjectType&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;objectType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;recordId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getSObjectType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is a format example, not the ID of a record to query in your org.&lt;/p&gt;

&lt;h2 id=&quot;how-the-extra-three-characters-are-calculated&quot;&gt;How the extra three characters are calculated&lt;/h2&gt;

&lt;p&gt;Split the 15-character ID into three groups of five. Within each group, mark each uppercase letter with a bit. The first character corresponds to bit zero, the second to bit one, and so on. Lowercase letters and digits contribute zero.&lt;/p&gt;

&lt;p&gt;That produces a number from 0 to 31. Use it as an index into:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;ABCDEFGHIJKLMNOPQRSTUVWXYZ012345
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Append the three resulting characters, one for each group, to the original ID. This equivalent bit-based explanation avoids having to reverse the groups manually.&lt;/p&gt;

&lt;h2 id=&quot;conversion-options&quot;&gt;Conversion options&lt;/h2&gt;

&lt;h3 id=&quot;formula-field&quot;&gt;Formula field&lt;/h3&gt;

&lt;p&gt;For an export or report, a Text formula field can expose the case-safe ID:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;CASESAFEID(Id)
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Salesforce documents the function in &lt;a href=&quot;https://help.salesforce.com/s/articleView?id=customize_functions_casesafeid.htm&amp;amp;language=en_US&amp;amp;type=5&quot;&gt;CASESAFEID&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;apex&quot;&gt;Apex&lt;/h3&gt;

&lt;p&gt;Use the platform’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Id&lt;/code&gt; type instead of reimplementing the suffix calculation:&lt;/p&gt;

&lt;div class=&quot;language-apex highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;n&quot;&gt;Id&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;recordId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;valueOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inputId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;outputId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;valueOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;recordId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Handle invalid input at the integration boundary. A value having the right length does not establish that the record exists or that the current user may access it.&lt;/p&gt;

&lt;h3 id=&quot;javascript&quot;&gt;JavaScript&lt;/h3&gt;

&lt;p&gt;If you need the calculation outside Salesforce, this implementation converts a correctly capitalized, 15-character alphanumeric input:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;to18CharacterId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/^&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;a-zA-Z0-9&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;]{15}&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;$/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;TypeError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Expected a 15-character Salesforce ID&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;alphabet&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ABCDEFGHIJKLMNOPQRSTUVWXYZ012345&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;suffix&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mask&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;position&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;position&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;character&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;character&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;character&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Z&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;mask&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;suffix&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;alphabet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;suffix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The format check deliberately accepts only 15 characters. It does not validate an existing 18-character suffix or look up a Salesforce record.&lt;/p&gt;

&lt;h2 id=&quot;my-recommendation&quot;&gt;My recommendation&lt;/h2&gt;

&lt;p&gt;Use 18-character IDs at integration and export boundaries, preserve capitalization, and normalize the format before joining datasets. Check format, record existence, and authorization separately. Removing this ambiguity early is easier than diagnosing mismatched data later.&lt;/p&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
  &lt;p&gt;Originally published on 2016-04-16. English edition revised on October 3, 2026. Updated the conversion example and removed legacy tool recommendations and assumptions about internal ID structure. &lt;a href=&quot;/blog/2016/04/16/salesforce-15-and-18-char-reocrd-ids&quot;&gt;Read the Chinese original&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
      <pubDate>Sat, 16 Apr 2016 09:25:29 +0800</pubDate>
      <link>https://jairzh.com/en/blog/2016/04/16/salesforce-record-ids</link>
      <guid isPermaLink="true">https://jairzh.com/en/blog/2016/04/16/salesforce-record-ids</guid>
    </item>
    
  </channel>
</rss>
