
Tired of hand-writing deal summaries? This Agentforce prompt turns any Opportunity into a mini dashboard—HTML snapshot, key metrics, progress bar, blockers, action items, and AI observations—in seconds. Below you’ll find the prompt and two ways to surface it:
- Pre-rendered / Flow Trigger – cheapest on AI calls; runs only when a record changes.
- Just-in-time / Apex Getter – real-time every time the page loads; always-fresh insight.
Copy, tweak, and share!
1. The prompt (copy everything inside the block)
ROLE: Senior Deal Analyst
CONTEXT:
{!$Input:Opportunity.Name} worth {!$Input:Opportunity.Amount} closes {!$Input:Opportunity.CloseDate}
{!$RecordSnapshot:Opportunity.Snapshot}
INSTRUCTION: '''
GOAL
Generate a single Rich Text (HTML) “Opportunity Insight Panel” that will be pasted directly into a Salesforce Rich Text field.
CONTENT & LAYOUT
1. Executive Snapshot – 100–120-word paragraph summarising status, value, probability, and sentiment.
2. Key Metrics Row – one <div> with four coloured “stat chips” (Amount, Stage, Close Date, Win Prob %).
3. Progress Bar – full-width bar showing Win Prob % fill.
4. Blockers Table – <table><thead><tr><th>Blocker</th><th>Owner</th><th>ETA</th></tr></thead><tbody>…</tbody></table>.
5. Next Actions – <ul> with 3-5 items.
6. AI Observations – <blockquote> highlighting risks or opportunities.
VISUAL / UX RULES
• Inline CSS only; ≤ 1 KB.
• Soft shadows, #f5f7fa panels, 8 px radius.
• Font sizes: 18 px heading, 14 px body, 12 px chips.
• Palette: #006BFF primary, #E24C4B danger, #15A362 success, #F5A623 warning, #333 text.
• Mobile friendly (width:100%, word-wrap).
• No external images; SVG ok < 2 KB.
RULES
• Never echo masked or redacted values.
• Output valid self-contained HTML only.
OUTPUT: Rich Text (HTML) ONLY
'''
2. Pre-rendered approach — Record-Triggered Flow
- Create a Record-Triggered Flow on Opportunity (Before-Save or After-Update).
- Add an Action: “Send Prompt” (Agentforce) and paste the prompt above.
- Store the response in a text variable, e.g. var_AI_HTML.
- Update Opportunity → AI_Output__c = var_AI_HTML.
- Activate. The field refreshes only when key fields change, so you conserve AI credits.
Use when:
- Second-by-second freshness isn’t required.
- Predictable, low cost matters.
- You’re happy to expose AI_Output__c on Lightning pages.
3. Just-in-time approach — Apex getter on page load
Need live content every time someone opens the record? Wire a lightweight Aura/LWC controller to Einstein LLM:
public with sharing class DisplayController {
public class RichTextResponse {
@AuraEnabled public Boolean success;
@AuraEnabled public String content;
@AuraEnabled public String error;
public RichTextResponse(Boolean s,String c,String e){
success = s; content = c; error = e;
}
}
@AuraEnabled(cacheable=false)
public static RichTextResponse getRichTextContent(String recordId) {
if (String.isBlank(recordId)) {
return new RichTextResponse(false,'','Record ID is required');
}
try {
Map<String,String> sess = new Map<String,String>{ 'id' => recordId };
ConnectApi.WrappedValue oppVal = new ConnectApi.WrappedValue();
oppVal.value = sess;
Map<String,ConnectApi.WrappedValue> inputs = new Map<String,ConnectApi.WrappedValue>{
'Input:Opportunity' => oppVal,
'Input:jsonObject' => new ConnectApi.WrappedValue('{}')
};
ConnectApi.EinsteinPromptTemplateGenerationsInput tplInput =
new ConnectApi.EinsteinPromptTemplateGenerationsInput();
tplInput.additionalConfig = new ConnectApi.EinsteinLlmAdditionalConfigInput();
tplInput.additionalConfig.applicationName = 'PromptBuilderPreview';
tplInput.inputParams = inputs;
ConnectApi.EinsteinPromptTemplateGenerationsRepresentation out =
ConnectApi.EinsteinLLM.generateMessagesForPromptTemplate(
'Senior_Deal_Analyst', tplInput);
if (out != null && !out.generations.isEmpty()) {
return new RichTextResponse(true, out.generations[0].text, '');
}
return new RichTextResponse(false,'','No response from LLM');
} catch (Exception ex) {
return new RichTextResponse(false,'','Error: '+ ex.getMessage());
}
}
}
Embed this in LWC, drop the returned HTML into <lightning-formatted-rich-text>, and you’re done.
<template>
<div class="rich-text-wrapper">
<!-- Compact loading indicator -->
<template if:true={isLoading}>
<div class="loading-container">
<lightning-spinner alternative-text="Loading..." size="small"></lightning-spinner>
<span class="loading-text">Loading analysis...</span>
</div>
</template>
<!-- Error message -->
<template if:true={error}>
<div class="error-message">
<lightning-icon icon-name="utility:error" size="x-small"></lightning-icon>
<span>{error}</span>
</div>
</template>
<!-- Rich text content -->
<template if:true={richTextContent}>
<div class="rich-text-container">
<div class="rich-text-content" lwc:dom="manual">
<!-- Rich text will be inserted here -->
</div>
</div>
</template>
<!-- Empty state -->
<template if:false={richTextContent}>
<div class="empty-state">
<lightning-icon icon-name="utility:info" size="small"></lightning-icon>
<span>No analysis available</span>
</div>
</template>
<!-- Compact refresh button -->
<div class="refresh-button-container">
<lightning-button-icon
icon-name="utility:refresh"
variant="bare"
size="small"
onclick={handleRefresh}
disabled={isLoading}
title="Refresh Analysis">
</lightning-button-icon>
</div>
</div>
</template>
import { LightningElement, api, track } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import getRichTextContent from '@salesforce/apex/DisplayController.getRichTextContent';
export default class RichTextDisplay extends LightningElement {
@api recordId;
@track richTextContent = '';
@track isLoading = false;
@track error = '';
connectedCallback() {
this.loadRichTextContent();
}
async loadRichTextContent() {
this.isLoading = true;
this.error = '';
try {
const result = await getRichTextContent({ recordId: this.recordId });
if (result && result.success) {
this.richTextContent = result.content;
this.renderRichText();
} else {
this.error = result.error || 'Failed to load content';
}
} catch (error) {
console.error('Error loading rich text content:', error);
this.error = error.body?.message || error.message || 'An error occurred while loading content';
} finally {
this.isLoading = false;
}
}
handleRefresh() {
this.loadRichTextContent();
}
renderRichText() {
const richTextContainer = this.template.querySelector('.rich-text-content');
if (richTextContainer && this.richTextContent) {
richTextContainer.innerHTML = this.richTextContent;
}
}
showToast(title, message, variant) {
const evt = new ShowToastEvent({
title: title,
message: message,
variant: variant
});
this.dispatchEvent(evt);
}
renderedCallback() {
// Render rich text after the component is rendered
if (this.richTextContent) {
this.renderRichText();
}
}
}
.rich-text-wrapper {
position: relative;
min-height: 100px;
}
.rich-text-container {
min-height: 100px;
height: auto;
overflow: visible;
border: 1px solid #dddbda;
border-radius: 0.25rem;
background-color: #ffffff;
margin-bottom: 0.5rem;
}
.rich-text-content {
padding: 0.75rem;
line-height: 1.5;
color: #080707;
}
.rich-text-content h1,
.rich-text-content h2,
.rich-text-content h3,
.rich-text-content h4,
.rich-text-content h5,
.rich-text-content h6 {
margin-top: 1rem;
margin-bottom: 0.5rem;
font-weight: 600;
color: #16325c;
}
.rich-text-content h1 { font-size: 1.5rem; }
.rich-text-content h2 { font-size: 1.25rem; }
.rich-text-content h3 { font-size: 1.125rem; }
.rich-text-content h4 { font-size: 1rem; }
.rich-text-content h5 { font-size: 0.875rem; }
.rich-text-content h6 { font-size: 0.75rem; }
.rich-text-content p {
margin-bottom: 0.75rem;
}
.rich-text-content ul,
.rich-text-content ol {
margin-bottom: 0.75rem;
padding-left: 1.5rem;
}
.rich-text-content li {
margin-bottom: 0.25rem;
}
.rich-text-content blockquote {
border-left: 4px solid #dddbda;
padding-left: 1rem;
margin: 1rem 0;
font-style: italic;
color: #706e6b;
}
.rich-text-content code {
background-color: #f3f2f2;
padding: 0.125rem 0.25rem;
border-radius: 0.125rem;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 0.875rem;
}
.rich-text-content pre {
background-color: #f3f2f2;
padding: 1rem;
border-radius: 0.25rem;
overflow-x: auto;
margin: 1rem 0;
}
.rich-text-content pre code {
background-color: transparent;
padding: 0;
}
.rich-text-content table {
width: 100%;
border-collapse: collapse;
margin: 1rem 0;
}
.rich-text-content th,
.rich-text-content td {
border: 1px solid #dddbda;
padding: 0.5rem;
text-align: left;
}
.rich-text-content th {
background-color: #f3f2f2;
font-weight: 600;
}
.rich-text-content a {
color: #0070d2;
text-decoration: none;
}
.rich-text-content a:hover {
text-decoration: underline;
}
.rich-text-content strong {
font-weight: 600;
}
.rich-text-content em {
font-style: italic;
}
.rich-text-content img {
max-width: 100%;
height: auto;
border-radius: 0.25rem;
margin: 0.5rem 0;
}
/* Loading state */
.loading-container {
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
min-height: 80px;
}
.loading-text {
margin-left: 0.5rem;
color: #706e6b;
font-size: 0.875rem;
}
/* Error state */
.error-message {
display: flex;
align-items: center;
padding: 0.5rem;
background-color: #fef7f7;
border: 1px solid #ea001e;
border-radius: 0.25rem;
color: #ea001e;
font-size: 0.875rem;
margin-bottom: 0.5rem;
}
.error-message lightning-icon {
margin-right: 0.5rem;
}
/* Empty state */
.empty-state {
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
color: #706e6b;
font-size: 0.875rem;
min-height: 80px;
}
.empty-state lightning-icon {
margin-right: 0.5rem;
}
/* Refresh button */
.refresh-button-container {
position: absolute;
top: 0.25rem;
right: 0.25rem;
z-index: 1;
}
/* Responsive design */
@media (max-width: 768px) {
.rich-text-container {
height: auto;
}
.rich-text-content {
padding: 0.5rem;
}
.loading-container,
.empty-state {
min-height: 60px;
}
}
Use when:
- Stakeholders demand real-time insight.
- Your org has generous LLM quotas.
- You don’t want to store generated output on the record.
4. Decide which model fits
- Cost: Pre-rendered wins (one call per change) vs. Just-in-time (one call per view).
- Freshness: Just-in-time wins (always current) vs. Pre-rendered (cached).
- Complexity: Flow is no-code; Apex requires dev work but is more flexible.
Many teams even mix both: Flow for baseline insight, Apex for an always-fresh KPI banner.
5. Share & iterate
Clone the prompt, brand the colours, extend the layout—then share your version with the community using #SalesforceAI and #FreePrompt. Happy automating!
#SalesforceAI #Agentforce #EinsteinGPT #PromptEngineering #OpportunityManagement #FlowBuilder #Apex #LWC #SalesOps #RevOps #CRM #Automation #RevenueIntelligence #GenerativeAI #Trailblazer #FreePrompt