Inbound Email to Actionable Task: Solution Pattern
Solution Patterns · Email & Activity Automation · Field Guide
Solution pattern

Inbound Email to Actionable Task

A reusable Salesforce pattern for turning incoming email into an assigned, prioritised Task, with an Email Service, a background job and Prompt Builder templates

Version
Pattern v1
Date
3 November 2026
Org
Any Salesforce org (reference build: “agent2”)

For solution architects, Salesforce admins and stakeholders who want to apply the pattern to their own inbox. Generalised from a working reference implementation (see the appendix). Developer detail is in the appendix.

1

At a glance

Requests still arrive by email: from customers, suppliers, partners and other systems. This pattern reads each one for you, links it to the record it belongs to, and leaves a clear, prioritised Task with the right person.

The problem

A shared inbox such as facilities@acme.example collects requests all day. Someone has to open each email, work out who sent it and what it's about, find the matching account, order or case, decide how urgent it is and pass it to the right person. It is slow, it depends on who is on shift, and requests get missed when the inbox is busy or the triager is away.

What the pattern does

Each email sent to a Salesforce Email Service address is matched to a record by its sender or a business key (for example Request #A-10234). A placeholder Task and the email's Files are saved at once. Then, in the background, a prompt template classifies and summarises the email, the answer is checked against allowed values, an assignment rule picks the owner, and the Task is finished: subject, priority, due date, category and owner.

1 → 1
one inbound email becomes one Task on one related record
✓ no duplicates by design
0
emails lost: every email ends up as a Task or a review log entry
✓ nothing silently dropped
Admin
configurable routing: owners come from a custom metadata table
✓ no code to re-route
AI advises
the AI suggests category and urgency; rules decide the owner
✓ validated, with defaults

These are design properties of the pattern, not measured results. Throughput and accuracy depend on your email volume, prompts and data.

Key ideas in one minute

  • Match first, with rules, not AI. The sender and business key are found by pattern or lookup. If nothing matches, the email goes to the review log; the pattern never guesses which record an email belongs to.
  • Fast first, smart second. The Task, the email and its Files appear on the record instantly. The AI work happens moments later in one background job (a Queueable), so a slow or failed AI call never loses an email.
  • The AI suggests; the rules decide. The prompt proposes a category, summary, actions, urgency and a due date. Apex checks each value against an allowed list and falls back to safe defaults. The owner always comes from your assignment rules.
  • A Flow carries files to the AI. Apex can't hand a File to a prompt template directly, so a small auto-launched Flow does that one step when attachments matter.
  • Admins own the knobs. Categories, routing, priorities and due dates live in custom metadata and prompt versions, not in code.

Use it when…

  • Requests arrive by email to a known address you can forward or redirect.
  • Most emails can be tied to a record by sender, account or a reference number.
  • The work is a follow-up for a person: a call, a check, a reply, a visit.
  • You want consistent triage and a clear owner, not a full support desk.
  • The categories and routing rules can be written down.

Don't use it when…

  • You need queues, SLAs, entitlements and threaded replies: use Email-to-Case and Omni-Channel.
  • Emails are conversations that need replies inside Salesforce.
  • Volumes are very high (thousands an hour) or files are very large.
  • The routing decision itself is a judgement call no rule can express.
  • Regulations don't allow email content to be sent to a generative AI model.
2

How it works

Steps 1–3 happen the moment an email arrives; steps 4–8 run moments later in one background job.

From inbox to assigned Task

Steps 1–3 are instant and use no AI: identify, match, save. Steps 4–8 run in one background job: read files, classify, validate, assign, update. Every failure branch writes to the review log.

Flowchart: how an inbound email becomes an assigned, prioritised Task: instant matching in the Email Service, then AI classification, validation and assignment in a background job An email is sent to the service address 1 Email reaches the Email Service From · subject · body · attachments read 2 Sender or business key identified? No Not processed: logged for review Unmatched / Review log · NO_KEY Yes 3 A related record matches that key? No Not processed: logged for review Unmatched / Review log · NO_MATCH Yes 3 Placeholder Task + email + Files saved Task “Processing…” · linked to the record 4 Attachments read (Flow + file prompt)? No Skipped or unreadable: carries on No files, or file noted on the Task for review Yes 5 Classify + summarise prompt answers? No Safe defaults, Task flagged Unmatched / Review log · AI_UNAVAILABLE Yes 6 AI output valid against allowed values? No Invalid values replaced by defaults Unmatched / Review log · INVALID_AI_OUTPUT Yes 7 An assignment rule matches? No Fallback default owner Unmatched / Review log · NO_ROUTING_RULE Yes 8 Task updated, owner notified Subject · priority · due date · owner · category Handed to one background job (Queueable) Assigned, prioritised Task On the record and in the owner’s My Tasks Unexpected error at any step Steps 1–3: Review log · PROCESSING_ERROR Steps 4–8: Task kept + flagged · Review log EMAIL SERVICE · INSTANT · NO AI BACKGROUND JOB · QUEUEABLE + PROMPT TEMPLATES
Green finished result Orange a person should look Grey skipped, nothing wrong Slate the background job

Each step in detail

When the email arrives Instant. No AI. Runs in the Email Service handler.
1
The email reaches the Email Service
The inbox (or a mail rule) forwards to the long, generated Email Service address. Salesforce calls your Messaging.InboundEmailHandler class with the sender, subject, plain and HTML body, headers and attachments.
Always acceptedthe handler never bounces the email
2
Identify the sender and the business key
Resolve the real sender (forwarded mail often hides it in a header such as X-Original-Sender or Reply-To). Then look for the key: a reference, ticket or order number by regular expression, e.g. #A-\d{5}, or a Contact/Account by email address or domain.
NO_KEYif neither is found: logged, nothing else happens
3
Find the related record and save the Task
Query the record that owns the key (Account, Case, Order, Work Order, a custom object). If one is found, create a placeholder Task (“Email received — processing…”) linked to it, and save the email and every non-empty attachment as Files on the record and the Task. Then enqueue the background job with the Ids.
NO_MATCHif no record has the key: loggedOtherwise: Task Not Started
↓ a few moments later, in one background job…
Background job A Queueable that makes every AI call first, then one Task update.
4
Read the attachments (optional)
For each saved File, the job starts a small auto-launched Flow that passes the File to a file prompt template. It returns a short summary of the document. Skip this step entirely if your emails' attachments don't change the decision.
No files: skip to 5an unreadable file is noted on the Task
5
Classify and summarise
The subject, sender, the first few thousand characters of the body, the related record's name and any file summaries go to the classify prompt template. It answers in a fixed format: CATEGORY, SUMMARY, ACTIONS, URGENCY and DUE_IN_DAYS.
Structured answerClassify prompt
6
Validate the AI output
Each value is checked: the category must be in the allowed list, urgency must map to a Task priority, due days must be a whole number in range. Anything missing or invalid is replaced by a safe default (e.g. category General, priority Normal, due in 2 business days) and flagged.
INVALID_AI_OUTPUTdefaults used; logged for prompt tuning
7
Assign an owner
The job looks up the routing rules (custom metadata) for the validated category and any other attributes, such as region or record type. A rule resolves to a named user, the related record's owner, or a round-robin member of a group. If no rule matches, or the user is inactive, the default owner is used.
NO_ROUTING_RULEdefault owner used; logged
8
Finish the Task and notify
One update: subject, description (summary, actions, file blocks, original email), priority, due date, category and owner. Optionally a custom notification or email alert tells the assignee.
Task assignedHigh = act soon

Why a background job, and why a Flow? An Email Service handler should finish quickly and must not lose the email if an AI call is slow or fails, so it only saves records and hands over. The Queueable makes every AI call before it writes anything, then updates the Task once, which keeps callouts and DML in a safe order. The Flow exists because the Apex route to prompt templates (ConnectApi) rejects File inputs, while the Flow runtime accepts a File natively.

What the Task looks like when it's finished

Subject: [Repair] Broken door closer, Level 3 east stairwell — Request #A-10234

Owner: Priya Shah (Facilities, North region) · Priority: High · Due: 5 Nov 2026 · Category: Repair · Related to: ACME Facilities · Name: Jordan Lee

Description, top to bottom: key, sender, received time and subject; SUMMARY; ACTIONS (one bullet each); an ATTACHMENT block for each file; any VALIDATION notes (e.g. “category defaulted”); then the ORIGINAL EMAIL (first 2,000 characters).

Example values are illustrative.

3

What gets created

For each matched email the pattern adds records to an existing related record. It doesn't create Accounts, Cases or Orders: the record that owns the key has to be there first.

RecordWhat it holdsWhere it's linked
Task (one per email)Placeholder: type Email, status Not Started, priority Normal. Finished by the background job with subject, description, priority, due date, category and owner.Related To (WhatId) = matched record; Name (WhoId) = sender's Contact
Files (one per attachment)Every non-empty attachment under its original file name, as a ContentVersion.The related record and the Task
Email record (optional)An EmailMessage with the full body, so the email shows in the activity timeline as an email. Recommended; the reference build didn't create one.The related record (RelatedToId) and the Task (ActivityId)
Unmatched / Review logOnly when the email can't be used, or the AI or routing needed a fallback: subject, sender, reason, details, body (up to 32,000 characters), received time and a link to the Task if one exists.Its own object and tab; optional lookup to the Task

The Task, field by field

FieldSet fromFallback
Subject[Category] + a one-line summary + the key, up to 255 charactersEmail subject, trimmed
DescriptionSummary, actions, file blocks, validation notes, original emailOriginal email only, with “manual review” note
PriorityURGENCY mapped to High / Normal / Low by the priority tableNormal (or High if the AI failed, so a person looks)
ActivityDate (due)Received date + DUE_IN_DAYS, capped by the category's maximum, skipping weekendsThe category's default days, else 2 business days
OwnerIdFirst matching routing ruleThe default owner user
Category__c (custom picklist)Validated CATEGORYGeneral
WhatId / WhoIdMatched record / sender's Contact (step 3, not AI)WhoId left blank
Needs_Review__c (optional checkbox)Ticked when any default was used—

Reasons on a review log entry

ReasonWhat happenedWhat to do
NO_KEYNo business key in the subject or body and the sender isn't known.File by hand; consider a new key pattern.
NO_MATCHA key was found, but no record has it. The key is in Details.Fix the record's key and resend, or file by hand.
AI_UNAVAILABLEThe prompt call failed or returned nothing. The Task exists with defaults.Read the Task; check Einstein setup.
INVALID_AI_OUTPUTA value was outside the allowed list. Defaults were used.Tune the prompt or add the category.
NO_ROUTING_RULENo rule matched, or the target user is inactive. Default owner used.Add or fix a routing row.
PROCESSING_ERRORSaving the Task or Files failed. Details holds the error.Tell your admin.
4

Where to see it in Salesforce

The everyday checks, step by step. Start from the App Launcher (the 9-dot grid, top left) or Setup (the gear, top right).

A. My new Tasks

Assignees
App Launcher›Tasks›List view: My Tasks
  1. Sort by Due Date, then Priority. Add the Category column to the list view.
  2. Read SUMMARY and ACTIONS, open the Related To record, and mark the Task Completed when done. Not yours? Reassign it and tell your admin.
You should see: a subject starting with a category in brackets, not “processing…”. A ticked Needs Review means the AI or routing used a default.

B. The email on its related record

Everyone
App Launcher›Accounts / Orders / …›the record›Activity
  1. Find the record by its key (e.g. search A-10234).
  2. On the Activity timeline, open the newest Task of type Email (and the Email, if you save one).
  3. Open Related › Files to preview attachments; the same Files are on the Task.
You should see: one Task per email, with its owner and due date, and one File per attachment.

C. The review list for unmatched emails

Triage lead
App Launcher›Inbound Email Reviews›List view: Open
  1. Show Subject, From Address, Reason, Details, Received Date and Task. Sort newest first.
  2. Open a row, decide where it belongs, act, then set its status to Resolved.
  3. Review the counts by Reason weekly: they tell you which rule or prompt to improve.
You should see: a reason on every row. Many NO_MATCH = records missing their key; many INVALID_AI_OUTPUT = tune the prompt.

D. Admin setup

Admins
Setup›Email Services›your service
  1. Points to your handler class, is Active, accepts the attachments you need, and has an active address with the right Context User.
Setup›Prompt Builder›classify / file templates + Flows›attachment Flow
  1. Each template has an Active version (templates deployed without one arrive inactive). The Flow is Active, variable names unchanged.
Setup›Custom Metadata Types›Task Routing Rule›Manage Records
  1. Edit routing rows, categories, priorities and due-date defaults here. Changes apply to the next email, no deployment needed.
Setup›Apex Jobs
  1. A Task stuck on “processing…”? Find the Queueable job at the time the email arrived and read its status and error.
5

Configuration and how to adapt it

The pattern is the same everywhere; seven decisions make it yours. Work through them in order: each one feeds the next.

Make it yours: checklist. ☐ Business key   ☐ Matching rule   ☐ Categories   ☐ Priority and due dates   ☐ Prompts   ☐ Assignment rules   ☐ Address and permissions

1. Choose the business key

The key is what ties an email to exactly one record. Pick the most reliable thing your senders already include.

  • A reference number in the subject or body: request, ticket, order, invoice, claim. Best when senders reply to something you sent them.
  • The sender's email address → Contact → Account. Works for known customers, but one person can belong to several records.
  • The sender's domain → Account. A fallback only: shared domains (gmail.com) must be excluded.

Store the key on the record in a dedicated, indexed field (ideally External ID or Unique), in one normalised format, before emails arrive.

2. Write the matching rule

Decide the order of checks and what wins on a tie. A typical rule: key in subject → key in body → sender's Contact → review log.

  • Make the regular expression specific: (?i)\bRequest\s*#?\s*(A-\d{5})\b beats “any 7 digits”, which also matches phone and PO numbers.
  • Normalise before matching: strip prefixes, suffixes and spaces (#A-10234 → A-10234).
  • On more than one match, pick the newest open record and say so in the description, or send it to review. Never pick at random.

3. Define the categories

Keep the list short (5–10), mutually exclusive and named in the business's words. Each category drives routing, priority and due date. Always include a catch-all.

Example for ACME Facilities: Repair Cleaning Access & Keys Safety Booking Billing General

4. Choose priority and due-date rules

Each category gets a default priority and due date. The AI's URGENCY can adjust them; bound the due date per category so the AI can't set something unreasonable.

CategoryDefault priorityDefault due (business days)Max dueAI may raise to High?
SafetyHigh0 (today)1Always High
RepairNormal210Yes
BillingNormal515No
GeneralNormal210Yes

5. Write the prompts

One classify template (Flex, text inputs) and, if needed, one file template (Flex, one File input, called only by the Flow). Ask for a strict, line-based answer that Apex can parse. A skeleton, with placeholders in [BRACKETS]:

You triage inbound email for [TEAM NAME] at [COMPANY].
The email relates to: {!$Input:recordName} ([RECORD TYPE]).

From: {!$Input:emailSender}
Subject: {!$Input:emailSubject}
Body (may be truncated): {!$Input:emailBody}
Attachment summaries: {!$Input:fileSummaries}

Answer with exactly these five lines and nothing else:
CATEGORY: one of [Repair | Cleaning | Access & Keys | Safety | Booking | Billing | General]
SUMMARY: 1-2 sentences: who wants what, where, by when.
ACTIONS: semicolon-separated, specific next steps for the assignee.
URGENCY: one of [High | Normal | Low]. High only if [YOUR URGENCY RULES,
  e.g. safety risk, building access blocked, a deadline within 48 hours].
DUE_IN_DAYS: a whole number from 0 to [MAX], or DEFAULT if no date is implied.

Rules: never invent names, numbers or dates. If unsure, use General / Normal / DEFAULT.

Save, test in Prompt Builder with real (anonymised) emails, then activate the version. Keep the field names exactly as the parser expects.

6. Define the assignment rules table

A custom metadata type, e.g. Task_Routing_Rule__mdt, read top-down by Order; the first row whose filled-in columns all match wins. Blank means “any”.

OrderCategoryRegionRecord typeAssign toTarget
10SafetyUsersafety.lead@acme.example
20RepairNorthUserpriya.shah@acme.example
30RepairRound robinGroup: Facilities_Techs
40BillingRecord owner(the Account owner)
99Defaulttriage.lead@acme.example
  • Record owner: the related record's OwnerId, if active. Default: always keep one catch-all row with an active user.
  • Round robin: the next active member of a public group; store the last index in a custom setting or a small counter record.
  • Store users by username, not by Id, so the table survives sandbox refreshes.

⚠ Tasks can't be owned by queues. A Task's owner must be a user. For a team inbox: assign to a designated triage user, use round robin within a group, or create a Case or custom “Request” record owned by a queue and routed with Omni-Channel, with the Task as the follow-up.

7. Set the email service address and permissions

  • Create the Email Service with your handler class, the attachment types you need and a clear prefix (e.g. facilities). Forward facilities@acme.example to the generated address; restrict Accept Email From if senders are known.
  • Set the Context User to a dedicated integration user, and give it a permission set: create/edit Task, create Files, create the review log, read the related objects and key fields, run the Flow, and use Prompt Builder templates.
  • Give assignees read access to the related records, or they'll own Tasks they can't follow.
6

Testing

Test in three layers: fast unit tests with a fake AI answer, one bulk test, and end-to-end scenarios that send real emails. The reference build showed why the first layer matters: its tests drifted away from the code and stopped proving anything.

Recommended automated tests

Test classWhat it checksHow
EmailHandler testKey found in subject / body; normalisation; no key → NO_KEY; no record → NO_MATCH; Task and Files created and linked; sender resolved from forwarding headers; the job is enqueued.Build a Messaging.InboundEmail in the test; call handleInboundEmail
Queueable testParser reads all five lines; invalid category → default + flag; AI failure → AI_UNAVAILABLE and High; each routing type; inactive user → default owner; due date capped and skips weekends.A mock prompt result injected through a test hook; Test.startTest/stopTest runs the job
Bulk test200 emails in one transaction, mixed keys; no governor limit errors; one Task per email.Queries outside loops; one enqueue per email or a batched job
Routing testRule order, blank = any, first match wins.Read rules through a selector you can stub, since custom metadata can't be inserted in tests

Lessons from the reference build. Its handler test set up records on one field while the code matched on another, so the “matched” tests found nothing; its job test called a method that had been removed and stopped compiling. Keep the match field, method signatures and answer format in one place (constants or a small config class) that tests use too, and run the tests on every change.

Prompt templates can't be called from tests, so always route the AI call through a single method with a mock hook. Salesforce needs 75% coverage to deploy Apex to production; aim for every branch on the flowchart.

End-to-end test: send it emails

1

Prepare records

Create a test Account “ACME Facilities” with a request record keyed A-10234, a Contact jordan.lee@customer.example, and routing rows for Repair and Default.

2

Send the emails

Send each scenario below to the Email Service address, or to the business inbox if forwarding is set up, so the forward path is tested too.

3

Check the results

Refresh straight away, then again after a minute. Use paths A–C in section 4.

Scenarios to run
A

Happy path

Task assigned
You should see: at once, a “processing…” Task on the record. Shortly after: category Repair, High or Normal priority, a due date within the cap, owner from the Repair rule, Needs Review unticked.
B

No key, unknown sender

NO_KEY
You should see: no Task, and a review log row with Reason NO_KEY.
C

Key with no matching record

NO_MATCH
You should see: no Task, and a review log row with Reason NO_MATCH and A-99999 in Details.
D

Attachment carries the detail

File block on the Task
You should see: the PDF in Files on the record and the Task, an ATTACHMENT block with its summary, and a category that reflects the report, not just the one-line body.
E

Ambiguous intent

Defaults + review flag
You should see: a valid category (or General), every action listed, and, if any value was defaulted, Needs Review ticked and an INVALID_AI_OUTPUT row. Never a category outside the list.
F

Assignment fallback

NO_ROUTING_RULE
You should see: with no Booking rule (or its user deactivated for the test), the Task owned by the default owner and a NO_ROUTING_RULE row naming the category.

Also worth running: an HTML-only email, a forwarded email (sender in the header), a duplicate of scenario A, and an email with ten attachments, to see your own limits.

7

Known limits and design considerations

What to plan for before relying on it. Most were met while building the reference implementation; the rest are platform limits every build will hit.

Prompt templates can't take Files from Apex

The ConnectApi route rejects File inputs. Pass the ContentDocument Id to an auto-launched Flow, which hands the File to the prompt. Keep the Flow active and its variable names fixed; check the file template has an active version after every deploy.

Callout and Queueable limits

Each prompt call is a callout-like request with its own time and count limits per transaction. One file call per attachment adds up. Cap the number of files analysed, chain jobs for the rest, and keep all calls before any DML. The Email Service itself can enqueue only so many jobs per transaction.

Email size and attachments

Email Services cap the total message size (roughly 25 MB including attachments) and can be set to drop attachments or text parts. Very large files may also exceed what a prompt can read. Truncate the body sent to the AI (e.g. 3,000 characters) and keep the full text in the email record.

The Email Service runs as one user

Everything is created by the address's Context User, so it needs a permission set for every object, field, Flow and template involved. If that user is frozen or loses a licence, processing stops. Use a dedicated integration user.

Tasks can't be owned by queues

Pick a user (named, record owner, round robin or default). For true team queues, SLAs or Omni-Channel, route a Case or custom object instead and keep the Task as the follow-up.

AI output varies

The same email can get a slightly different summary or urgency. Validate every value, use defaults, and never let free text from the AI become an Id, a field name or an owner. Re-test the prompt after every model or version change.

Duplicates and threads

Senders reply-all, forward twice or resend. Store the Message-ID (and In-Reply-To) and skip, or append to the existing Task, when it's seen before. Decide whether a reply on a closed Task reopens it or creates a new one.

HTML-only and odd formats

Some mail has no plain-text body. Fall back to the HTML body with tags stripped. Signatures, disclaimers and long quoted threads confuse the AI: cut at the first quoted reply before sending.

PII and compliance

Email bodies and files go to a generative model through the Einstein Trust Layer. Confirm data masking, retention and your industry rules (health, finance, children's data) before enabling, and exclude categories of email that must not be processed.

Einstein request costs

Every prompt call consumes Einstein requests (credits). Cost scales with emails × (1 + files). Filter out auto-replies and newsletters before the AI step, and skip file analysis when it doesn't change the result.

8

Safety & rollback

Designed so that the worst case is “the email is on the record, logged, and a person reads it”.

Nothing is lost

Every email ends as a Task or a review log row with its body and reason. The handler always reports success to the mail server, so senders don't get bounces and nothing loops.

The AI never assigns outside the rules

The AI only proposes a category and urgency, both checked against allowed lists. The owner always comes from the routing table or the default owner, never from AI text.

Matching is deterministic

Which record an email belongs to is decided by your key and lookup, before any AI runs. No match means review, never a guess.

It only adds

It creates a Task, Files and log rows, and updates only the Task it created. It never changes the related record's own fields.

How to switch it off

  1. Stop all processing: Setup › Email Services › your service › untick Active (on the service or its address). Set what happens to mail sent while it's off (bounce, discard or requeue), or point the inbox forwarding back to people first.
  2. Keep Tasks, stop the AI: build in a switch, e.g. a custom setting AI_Enabled__c. When off, the job skips the prompts and uses defaults, so Tasks still arrive and get routed.
  3. Change routing at once: edit or reorder rows in the routing custom metadata. No deployment needed.
  4. Go back a prompt version: activate the earlier version in Prompt Builder. Test it first if the answer format changed.

Switching off doesn't undo anything: Tasks, Files and log rows already written stay as they are.

Before production

  1. All flowchart branches covered by unit tests with a mocked prompt; a bulk test passes; coverage over 75%.
  2. Scenarios A–F run in a sandbox with real, anonymised emails from each sender type.
  3. The key field exists, is indexed and is filled in on every record before its emails arrive.
  4. Prompt templates and the Flow are active in the target org (check after deploying).
  5. Routing table reviewed by the business; default owner is an active user; assignees can see the related records.
  6. Context User has its permission set; Einstein is enabled; the request budget is agreed.
  7. Duplicate handling (Message-ID) and the AI switch are in place.
  8. Privacy review done; a named owner checks the review list daily for the first weeks.
A

Appendix: technical reference

For admins and developers. Names below are a suggested blueprint; rename to your conventions, then keep the names in one constants class used by code and tests.

Components

ComponentTypePurpose
InboundEmailTaskHandlerApex (Messaging.InboundEmailHandler)Resolve sender; extract and normalise the key; find the record; placeholder Task; ContentVersion per attachment with ContentDocumentLinks to record and Task; optional EmailMessage; enqueue the job; log unmatched; always return success
InboundEmailTaskJobApex (Queueable, Database.AllowsCallouts)Run the attachment Flow per File; call the classify prompt; validate; resolve owner; one Task update; write review rows for fallbacks
InboundEmailUtilsApexresolveOriginalSender, extractKey, invokePromptTemplate (via ConnectApi.EinsteinLLM.generateMessagesForPromptTemplate) with a mockResult test hook, parseResponse, validate, logReview
TaskRouterApexReads routing rules through a stubbable selector; record owner, named user, round robin, default; checks the user is active
Analyze_Email_AttachmentAuto-launched FlowinputContentDocumentId → Get ContentDocument → Invoke Prompt Template → outputAnalysisText
Inbound_Email_ClassifyPrompt template (Flex)Text inputs: emailSubject, emailBody, emailSender, recordName, fileSummaries; five-line answer
Inbound_Email_File_SummaryPrompt template (Flex)One input of type File (ContentDocument); called only by the Flow
Inbound_Email_Review__c + tabCustom objectReview log: Subject, From, Reason (picklist), Details, Body (long text), Received, Message-ID, Task lookup, Status
Task_Routing_Rule__mdtCustom metadata typeOrder, Category, Region, Record Type, Assign To, Target, Active
Task_Category_Setting__mdtCustom metadata typeCategory, default priority, default and max due days, may-raise-to-High
Task.Category__c, Needs_Review__cCustom fieldsValidated category; fallback flag
Inbound_Email_Task_IntegrationPermission setFor the Email Service Context User
*Test classesApex testsHandler, job, router, bulk (section 6)
Email ServiceSetup configurationHandler class, attachments accepted, address prefix, Context User, Accept Email From

Reference implementation

Document 811

The Document 811 solution (org “agent2”) applies this pattern to utility locate notices. It matches each email to a Work Order by a 7+ digit 811 ticket number found in the subject or body, saves the PDF tickets as Files, and in a Queueable calls an email prompt and, through an auto-launched Flow, an attachment prompt per PDF. The Task's priority is High when action is required. It doesn't yet include the validation, routing table, email record or duplicate handling described here; those are the generalisations this pattern adds. Its own solution guide covers it in detail.

Glossary

TermMeaning
Email ServiceA Salesforce feature that gives you a generated email address and runs an Apex class for every message it receives.
Business keyThe value that ties an email to one record: a reference number, the sender's email or domain.
Related recordThe record the Task is linked to through Related To (WhatId), e.g. an Account, Case or Order.
QueueableAn Apex background job. Here it does all the AI work after the email is saved.
Prompt templateA reusable instruction for a generative AI model, built and versioned in Prompt Builder.
Auto-launched FlowA Flow with no screen or trigger, started from Apex. Here it passes a File to a prompt template.
Custom metadataAdmin-editable configuration records (__mdt) that deploy with the code and can be changed without a deployment.
Round robinAssigning work to members of a group in turn.
Context UserThe user an Email Service address runs as; it creates every record.
Einstein Trust LayerSalesforce's controls (masking, zero data retention, audit) between your data and the AI model.
Einstein requestsThe usage unit consumed by each generative AI call.
Inbound Email to Actionable Task · Solution pattern v1 · 3 November 2026 Back to top ↑