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

  1. Create a Record-Triggered Flow on Opportunity (Before-Save or After-Update).
  2. Add an Action: “Send Prompt” (Agentforce) and paste the prompt above.
  3. Store the response in a text variable, e.g. var_AI_HTML.
  4. Update Opportunity → AI_Output__c = var_AI_HTML.
  5. 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.
Article content

3. Just-in-time approach — Apex getter on page load

Article content

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