Exploring File Processing Capabilities in Salesforce : Meera R Nair

Exploring File Processing Capabilities in Salesforce
by: Meera R Nair
blow post content copied from  Meera R Nair - Salesforce Insights
click here to view original post


In this blog post series, we will explore the different file processing capabilities available in Salesforce, including:

  • Prompt Builder

  • Document AI

  • Intelligent Document Reader

Before diving into the implementation details, let’s start with a comparison of these options to understand their unique features and use cases.

Comparison: 



 Use Case: Automating Case Status Update with Prompt Builder

Let’s implement the following use case using Prompt Builder:

A customer has purchased a laptop and wants to create a case for a hardware issue — for example, the battery is not working. The customer sends an email to the support address, and the system should automatically create a case in Salesforce.

Along with the email, the customer attaches two documents:

  • Product Invoice

  • Warranty Card

The requirement is to parse both documents and extract the following key details:

  • Customer Name

  • Product Description

  • Serial Number

  • Date of Purchase (from the invoice)

  • Warranty-related details (from the warranty card)

Once the data is extracted, it must be validated against Salesforce records:

  1. Check for a valid Asset record matching the provided serial number.

    • If no matching asset is found, update the Case Product Status (custom field) as “Product Not Found.”

  2. If a valid asset exists, verify whether it falls within the warranty period by checking the related Entitlement record.

    • If the asset is within the warranty period, update Case Product Status to “In Warranty.”

    • Otherwise, update it to “Out of Warranty.”


 Implementation Details

Implementation involves below steps:

  1. Set up Email to Case
  2. Create Prompt Builder Template for file parsing
  3. Record Triggered Flow on Case creation
  4. Invocable Apex Class Creation for json parsing and validations
  • Set up Email to Case
If you have not done this in the past, please follow this Salesforce help article to understand how to set up Email to Case.


  • Create Prompt Builder Template for file parsing
Create a new prompt template of type flex:


Give an input parameter to accept case record.

Keep the default on model selection and proceed with creating the prompt.


Make sure to use insert resource and select the related atatchments(combined attachments) to get the files for processing.




In the prompt ask the builder to retrieve the data in json format so that the flow can then process this.

Now let us preview this prompt builder action by giving a test case record.

For that either use emailtocase or manually create a case with specific attachments.


see the attachments:


Please see screenshotts of attached documents:

Laptop invoice:




warranty:



Now, click on preview in the prmpt builder and enter the created case number:



see the response generated by the preview below:



This makes sure that the prompt is working as expected.
  • Record Triggered Flow on Case creation
Now let us create a record triggered flow on case to call the prompt template for processing the attachments in the case.

Keep the criteria for flow execution simple so that it’s easy to test the setup.

  1. Add an Action — Create an action named RetrieveWarrantyDetails to invoke the Prompt Builder template for processing the attached files.

  2. Pass Parameters — Pass the Case record as a parameter to the action.

  3. Store Response — Capture the response JSON text from the Prompt Builder in a flow variable (e.g., response).



  • Invocable Apex Class Creation
To handle the JSON parsing and required validations, I have created an Invocable Apex Class (fully generated using GitHub Copilot).
This class accepts the JSON text and Case as input parameters, performs the necessary validations, and updates the product status accordingly.
***************
public with sharing class CaseWarrantyFlowHandler {
    
    // Inner class to represent the product details structure
    public class ProductDetails {
        public String CustomerName;
        public String ProductDescription;
        public String SerialNumber;
        public Date DateOfPurchase;
        public Date WarrantyStartDate;
        public Date WarrantyEndDate;
    }
    
    // Request wrapper class for Flow input
    public class Request {
        @InvocableVariable(required=true)
        public Id caseId;
        
        @InvocableVariable(required=true)
        public String prddetails;
    }
    
    @InvocableMethod(label='Evaluate Case Warranty' description='Parses product details JSON and updates Case status based on Asset and Entitlement validation')
    public static void evaluateWarranty(List<Request> requests) {
        if (requests == null || requests.isEmpty()) {
            return;
        }
        
        // Collect Case Ids for bulk processing
        Set<Id> caseIds = new Set<Id>();
        for (Request r : requests) {
            if (r != null && r.caseId != null) {
                caseIds.add(r.caseId);
            }
        }
        
        if (caseIds.isEmpty()) {
            return;
        }
        
        // Query Cases with Account (Person Account model)
        Map<Id, Case> caseMap = new Map<Id, Case>([
            SELECT Id, AccountId, ContactId, Status
            FROM Case
            WHERE Id IN :caseIds
        ]);
        
        // Parse JSON and prepare cases for update
        List<Case> casesToUpdate = new List<Case>();
        Date today = Date.today();
        
        for (Request request : requests) {
            if (request == null || request.caseId == null) {
                continue;
            }
            
            Case currentCase = caseMap.get(request.caseId);
            if (currentCase == null) {
                continue;
            }
            
            // Parse JSON to ProductDetails
            ProductDetails productInfo = parseProductDetails(request.prddetails);
            if (productInfo == null || String.isBlank(productInfo.SerialNumber)) {
                // Invalid JSON or missing serial number
                casesToUpdate.add(new Case(Id = currentCase.Id, Product_status__c = 'Product not found'));
                continue;
            }
            
            // Determine customer account ID
            Id customerAccountId = getCustomerAccountId(currentCase, productInfo);
            if (customerAccountId == null) {
                casesToUpdate.add(new Case(Id = currentCase.Id, Product_status__c = 'Product not found'));
                continue;
            }
            
            // Query Asset by Serial Number and Customer Account
            Asset matchingAsset = findAssetBySerialNumber(productInfo.SerialNumber, customerAccountId);
            if (matchingAsset == null) {
                casesToUpdate.add(new Case(Id = currentCase.Id, Product_status__c = 'Product not found'));
                continue;
            }
            
            // Check warranty status
            String warrantyStatus = checkWarrantyStatus(matchingAsset, productInfo, today);
            casesToUpdate.add(new Case(Id = currentCase.Id, Product_status__c = warrantyStatus));
        }
        
        // Bulk update cases
        if (!casesToUpdate.isEmpty()) {
            try {
                update casesToUpdate;
            } catch (DmlException e) {
                System.debug('Error updating cases: ' + e.getMessage());
            }
        }
    }
    
    // Parse JSON string to ProductDetails object
    private static ProductDetails parseProductDetails(String jsonString) {
        if (String.isBlank(jsonString)) {
            return null;
        }
        
        try {
            System.debug(jsonString);
             String cleanJsonString = jsonString.replace('\u00A0', ' ');
            //String jsonupdated =  jsonString.replace('(Intel i5, 16GB RAM, 512GB SSD)', '');
            jsonString.remove('(Intel i5, 16GB RAM, 512GB SSD)');
            System.debug(cleanJsonString);
            return (ProductDetails) JSON.deserialize(cleanJsonString, ProductDetails.class);
        } catch (Exception e) {
            System.debug('Error parsing JSON: ' + e.getMessage());
            return null;
        }
    }
    
    // Determine customer account ID from Case or ProductDetails
    // Assumes Person Account model where Case.AccountId is populated with Person Account
    private static Id getCustomerAccountId(Case currentCase, ProductDetails productInfo) {
        // Priority 1: Case AccountId (Person Account)
        // In Person Account model, this will be the Person Account ID
        if (currentCase.AccountId != null) {
            return currentCase.AccountId;
        }
        
        // Priority 2: Find Person Account by Customer Name from JSON
        if (!String.isBlank(productInfo.CustomerName)) {
            try {
                Account customerAccount = [
                    SELECT Id 
                    FROM Account 
                    WHERE Name = :productInfo.CustomerName
                    AND IsPersonAccount = true
                    LIMIT 1
                ];
                return customerAccount.Id;
            } catch (QueryException e) {
                System.debug('Person Account not found for customer: ' + productInfo.CustomerName);
            }
        }
        
        return null;
    }
    
    // Find Asset by Serial Number and Customer Account
    private static Asset findAssetBySerialNumber(String serialNumber, Id accountId) {
        try {
            return [
                SELECT Id, AccountId, SerialNumber, Product2Id
                FROM Asset
                WHERE SerialNumber = :serialNumber
                AND AccountId = :accountId
                LIMIT 1
            ];
        } catch (QueryException e) {
            System.debug('Asset not found for serial number: ' + serialNumber);
            return null;
        }
    }
    
    // Check warranty status based on Entitlement
    private static String checkWarrantyStatus(Asset asset, ProductDetails productInfo, Date today) {
        // Query Entitlement for the asset's account
        List<Entitlement> entitlements = new List<Entitlement>();
        
        try {
            entitlements = [
                SELECT Id, StartDate, EndDate, AccountId
                FROM Entitlement
                WHERE AccountId = :asset.AccountId
                AND assetid = :asset.id
               // AND StartDate = :productInfo.WarrantyStartDate
                //AND EndDate = :productInfo.WarrantyEndDate
                LIMIT 1
            ];
        } catch (QueryException e) {
            System.debug('Error querying entitlements: ' + e.getMessage());
        }
        
        // Check if matching entitlement exists and is still valid
        if (!entitlements.isEmpty()) {
            Entitlement entitlement = entitlements[0];
            if (entitlement.EndDate != null && entitlement.EndDate >= today) {
                return 'In warranty';
            }
        }
        
        return 'Out of warranty';
    }
}

***************

And now let us call this from the flow:



Validation Output

We’re done! The file processing is now much simpler.

After creating a Case and saving it, you can observe that the Product Status field is automatically updated with the correct value.




Observations / Considerations

While experimenting with Prompt Builder multi-model processing, I noticed the following:

  1. Model Performance:
    It’s worth trying different models in Prompt Builder to compare responses.

    • For simple files, OpenAI GPT-4 performs well.

    • For complex PDFs or images, Gemini 2.5 Flash provided more accurate and reliable results.

  2. JSON Clean-Up:
    Occasionally, the generated JSON may contain unwanted or special characters, which can cause parsing issues in the Apex class.
    To address this, I added the following code snippet to clean up the JSON string:

    
    
    String cleanJsonString = jsonString.replace('\u00A0', ' ');
  3. Access Issues:
    At times, Prompt Builder displayed access-related issues when trying to retrieve Case records or associated files.


References:

https://developer.salesforce.com/blogs/2025/05/unlock-multi-modal-ai-with-file-inputs-in-prompt-builder 

Stay tuned! In the next blog post, we’ll dive into file processing with Document AI.

November 02, 2025 at 12:04PM
Click here for more details...

=============================
The original post is available in Meera R Nair - Salesforce Insights by Meera R Nair
this post has been published as it is through automation. Automation script brings all the top bloggers post under a single umbrella.
The purpose of this blog, Follow the top Salesforce bloggers and collect all blogs in a single place through automation.
============================

Salesforce