apex-email-services

Use this skill when implementing inbound email processing via Apex: parsing emails sent to a Salesforce-hosted address, creating or updating records from email content, handling attachments, or configuring Email Service routing. Trigger keywords: InboundEmailHandler, email service address, handleInboundEmail, Messaging.InboundEmail, Email-to-Case alternative, process email in Apex. NOT for outbound email templates, Messaging.SingleEmailMessage, workflow email alerts, or Email-to-Case declarative setup.

Best use case

apex-email-services is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Use this skill when implementing inbound email processing via Apex: parsing emails sent to a Salesforce-hosted address, creating or updating records from email content, handling attachments, or configuring Email Service routing. Trigger keywords: InboundEmailHandler, email service address, handleInboundEmail, Messaging.InboundEmail, Email-to-Case alternative, process email in Apex. NOT for outbound email templates, Messaging.SingleEmailMessage, workflow email alerts, or Email-to-Case declarative setup.

Teams using apex-email-services should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/apex-email-services/SKILL.md --create-dirs "https://raw.githubusercontent.com/PranavNagrecha/AwesomeSalesforceSkills/main/skills/apex/apex-email-services/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/apex-email-services/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How apex-email-services Compares

Feature / Agentapex-email-servicesStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Use this skill when implementing inbound email processing via Apex: parsing emails sent to a Salesforce-hosted address, creating or updating records from email content, handling attachments, or configuring Email Service routing. Trigger keywords: InboundEmailHandler, email service address, handleInboundEmail, Messaging.InboundEmail, Email-to-Case alternative, process email in Apex. NOT for outbound email templates, Messaging.SingleEmailMessage, workflow email alerts, or Email-to-Case declarative setup.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

Related Guides

SKILL.md Source

# Apex Email Services

This skill activates when a practitioner needs to receive, parse, and act on inbound emails using Apex. It covers the full lifecycle: implementing `Messaging.InboundEmailHandler`, configuring the Email Service address in Setup, parsing body and attachment content, handling rejection/bounce behavior, and writing testable handlers. It does NOT cover outbound email (`Messaging.SingleEmailMessage`), email alerts, or the declarative Email-to-Case setup.

---

## Before Starting

Gather this context before working on anything in this domain:

- Confirm the org edition — Email Services limits differ: Developer/Professional orgs get fewer daily processing slots than Enterprise/Unlimited. The standard limit is 1,000 email messages per day per service address; check the org's actual allocation.
- Identify whether you need text parsing, HTML parsing, or binary attachment processing — these require different handler branches.
- The most common wrong assumption: practitioners expect `handleInboundEmail` to run as a specific user. It runs in **system context** — no user permission enforcement applies unless you explicitly switch context in your code.
- Email Services run synchronously per message. Governor limits apply to the full handler execution: 100 SOQL queries, 150 DML statements, 10 MB heap. Large attachments (up to 25 MB per email) can breach heap limits quickly.
- The Email Service address must be **activated** in Setup. An inactive address silently drops all inbound mail.

---

## Core Concepts

### Mode 1: The InboundEmailHandler Interface

`Messaging.InboundEmailHandler` is the contract for all Apex inbound email processing. Your class must implement exactly one method:

```apex
global Messaging.InboundEmailResult handleInboundEmail(
    Messaging.InboundEmail email,
    Messaging.InboundEnvelope envelope
) { ... }
```

The return value matters. If you set `result.success = false`, Salesforce either bounces the message back to the sender or drops it silently — controlled by the **Error Action** setting on the Email Service configuration in Setup. Always return a populated `InboundEmailResult`; a `null` return is treated as failure.

The `InboundEmail` object carries the full message:
- `email.subject` — subject line
- `email.fromAddress` / `email.fromName` — sender identity
- `email.plainTextBody` / `email.htmlBody` — body variants
- `email.toAddresses` / `email.ccAddresses` — recipient arrays
- `email.textAttachments` — list of `InboundEmail.TextAttachment`
- `email.binaryAttachments` — list of `InboundEmail.BinaryAttachment`

The `InboundEnvelope` carries transport-level metadata: `toAddress`, `fromAddress`, which can differ from the `To:` and `From:` headers.

### Mode 2: Email Service Configuration

Each Apex handler class is associated with one or more **Email Service** configurations in Setup > Email Services. Each configuration generates a unique `@[instance].salesforce.com` address. Key settings:

| Setting | Purpose |
|---|---|
| Active | Must be checked or all mail is dropped. |
| Accept Email From | Restrict to specific sender domains or addresses; leave blank to accept all. |
| Error Action | Controls what happens when `success = false` — Bounce, Discard, or Requeue. |
| Apex Class | Points to your `InboundEmailHandler` implementation. |
| Over Email Rate Limit | Action when the daily limit is reached — Bounce, Discard, or Requeue. |

You can create multiple Email Service addresses (each with a different configuration) backed by the same Apex class to support different routing scenarios (e.g., separate addresses per product line, each stamping a different record type on created Cases).

### Mode 3: Attachment Parsing

Two attachment types exist, mapped separately:

- `InboundEmail.TextAttachment`: has `.body` (String), `.fileName`, `.mimeTypeSubType`
- `InboundEmail.BinaryAttachment`: has `.body` (Blob), `.fileName`, `.mimeTypeSubType`

CSV, plain text, and XML files arrive as `TextAttachment` when the MIME type is text-based. Images, PDFs, and binary formats arrive as `BinaryAttachment`. Handlers must check both lists and apply defensive null checks — either list can be null if no attachments of that type exist.

Attachments are processed **synchronously** inside your handler's governor limit budget. A 25 MB binary attachment parsed into a `Blob` and then into a `String` (e.g., `EncodingUtil.base64Encode`) can exhaust heap quickly. Use chunked processing or consider deferring heavy attachment work to a `Queueable` called from within the handler.

---

## Common Patterns

### Pattern: Create-or-Update Record from Inbound Email

**When to use:** An external system sends structured emails (e.g., order confirmations, sensor alerts) and you need to upsert Salesforce records based on parsed email content.

**How it works:**
1. Implement `InboundEmailHandler` and parse `email.plainTextBody` or `email.subject` for a record identifier (e.g., an order number in the subject line).
2. Use `SOQL` to find an existing record by the extracted identifier.
3. Upsert or insert accordingly, populating fields from parsed email content.
4. Return `result.success = true` on success, or log errors and return `false` if parsing fails.

```apex
global class OrderEmailHandler implements Messaging.InboundEmailHandler {
    global Messaging.InboundEmailResult handleInboundEmail(
        Messaging.InboundEmail email,
        Messaging.InboundEnvelope envelope
    ) {
        Messaging.InboundEmailResult result = new Messaging.InboundEmailResult();
        try {
            String orderNum = extractOrderNumber(email.subject);
            if (String.isBlank(orderNum)) {
                result.success = false;
                result.message = 'No order number found in subject.';
                return result;
            }
            List<Order__c> orders = [
                SELECT Id FROM Order__c WHERE OrderNumber__c = :orderNum LIMIT 1
            ];
            Order__c order = orders.isEmpty() ? new Order__c(OrderNumber__c = orderNum) : orders[0];
            order.LastEmailBody__c = email.plainTextBody;
            order.LastEmailDate__c = System.now();
            upsert order OrderNumber__c;
            result.success = true;
        } catch (Exception e) {
            result.success = false;
            result.message = e.getMessage();
        }
        return result;
    }

    private String extractOrderNumber(String subject) {
        if (subject == null) return null;
        Pattern p = Pattern.compile('ORD-\\d+');
        Matcher m = p.matcher(subject);
        return m.find() ? m.group() : null;
    }
}
```

**Why not simpler approaches:** A Flow with Email-to-Case only creates Cases. Custom Apex is needed when you need to target arbitrary sObjects, apply complex parsing logic, or conditionally reject mail.

### Pattern: Async Attachment Processing via Queueable

**When to use:** Emails arrive with large or multiple binary attachments that would exceed heap or CPU limits if processed synchronously.

**How it works:**
1. In `handleInboundEmail`, extract only the attachment metadata and Blob data. Store the Blob in a `ContentVersion` record immediately (avoids re-processing the email).
2. Enqueue a `Queueable` job, passing the `ContentVersion` Id.
3. The Queueable performs the expensive parsing (CSV, base64 decode, PDF text extraction stubs, etc.) in a separate transaction with its own governor limits.
4. Return `result.success = true` from the handler immediately — the email is accepted, processing continues asynchronously.

**Why not synchronous:** A 10–15 MB binary attachment parsed in-line regularly hits the 12 MB heap limit for synchronous Apex. Moving the heavy work to a `Queueable` avoids the timeout and gives a separate 12 MB heap budget.

### Pattern: Sender-Based Routing with Multiple Service Addresses

**When to use:** Different senders or subject patterns need to create different record types or trigger different workflows, but you want a single Apex class.

**How it works:**
1. Create multiple Email Service configurations in Setup, each pointed at the same Apex class.
2. Pass a routing signal via the email address itself (e.g., `support-billing@...` vs `support-tech@...`) or encode routing in a Custom Setting keyed by `toAddress`.
3. Inside `handleInboundEmail`, read `envelope.toAddress` to determine routing context, then branch logic accordingly.

---

## Decision Guidance

| Situation | Recommended Approach | Reason |
|---|---|---|
| Customer email should create a Case automatically | Declarative Email-to-Case (Setup > Email-to-Case) | No code needed; native threading and routing built in |
| Email from external system should upsert a custom sObject | Apex Email Service with `InboundEmailHandler` | Email-to-Case only targets Cases; Apex has full DML access |
| Email has large binary attachments requiring custom parsing | Apex handler + Queueable for attachment processing | Avoids synchronous heap/CPU limits |
| Need to reject or bounce specific senders at processing time | Apex handler returning `result.success = false` | Email Services Error Action controls bounce/discard behavior |
| Outbound transactional emails to customers | `Messaging.SingleEmailMessage` or email templates + workflow | This skill covers inbound only |
| Email volume exceeds 1,000/day limit | Platform architecture review — consider middleware or batching | Email Services daily limit is per org edition; cannot be raised in code |

---


## Recommended Workflow

Step-by-step instructions for an AI agent or practitioner activating this skill:

1. Gather context — confirm the org edition, relevant objects, and current configuration state
2. Review official sources — check the references in this skill's well-architected.md before making changes
3. Implement or advise — apply the patterns from Core Concepts and Common Patterns sections above
4. Validate — run the skill's checker script and verify against the Review Checklist below
5. Document — record any deviations from standard patterns and update the template if needed

---

## Review Checklist

Run through these before marking work in this area complete:

- [ ] `InboundEmailHandler` class is `global` and implements the interface correctly
- [ ] `handleInboundEmail` always returns a non-null `InboundEmailResult`
- [ ] Email Service address is **active** in Setup > Email Services
- [ ] `Accept Email From` restriction is configured to prevent spoofing
- [ ] Attachment parsing handles null `textAttachments` and `binaryAttachments` lists defensively
- [ ] Handler has a `try/catch` block and sets `result.success = false` on unexpected exceptions
- [ ] Test class uses `Test.setFixedSearchResults` or constructs `InboundEmail` objects directly
- [ ] Daily volume estimate confirmed within org edition limit (default 1,000/day)
- [ ] Large attachment flows enqueue a `Queueable` instead of processing synchronously
- [ ] Error Action on the Email Service config is set intentionally (Bounce vs Discard)

---

## Salesforce-Specific Gotchas

Non-obvious platform behaviors that cause real production problems:

1. **System Context with No Sharing** — `handleInboundEmail` runs in system mode. There is no running user, no `with sharing` enforcement, and no record-level access control unless you explicitly call into a `with sharing` method. A handler that creates records using `insert` will bypass all sharing rules silently.
2. **Inactive Address Drops Mail Silently** — If the Email Service address is set to Inactive in Setup, inbound emails do not bounce and do not generate errors. They are silently discarded. Practitioners regularly deploy a handler, forget to activate the service address, and spend hours debugging a "no traffic" problem.
3. **Both Body Fields Can Be Null** — HTML-only emails leave `plainTextBody` null. Plain-text-only emails leave `htmlBody` null. Production handlers that assume one is always populated throw `NullPointerException`s on real mail. Always test both paths.

---

## Output Artifacts

| Artifact | Description |
|---|---|
| `InboundEmailHandler` Apex class | Global Apex class implementing `Messaging.InboundEmailHandler`, ready to associate with an Email Service configuration |
| Email Service configuration checklist | Verified settings in Setup > Email Services: active flag, accepted senders, error action, rate limit action |
| Attachment parsing strategy | Documented decision on sync vs async processing based on expected attachment size and volume |
| Test class | Apex test constructing `Messaging.InboundEmail` objects directly and asserting on DML outcomes |

---

## Related Skills

- **apex/governor-limits** — Synchronous handler execution consumes the same governor limits as any Apex transaction; consult this skill when attachment or SOQL load is high.
- **apex/apex-rest-services** — Use when the integration partner can push HTTP instead of email; REST is preferable for high-volume structured data exchange.
- **admin/email-templates-and-alerts** — Use for outbound email workflows. NOT a replacement for inbound email processing.
- **integration/rest-api-patterns** — Consider as an alternative integration channel if volume or reliability requirements exceed what Email Services can provide.

Related Skills

apex-managed-sharing-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Grant row-level access programmatically via __Share records when declarative sharing rules cannot express the policy. NOT for OWD, role hierarchy, or criteria-based sharing rule design.

lwc-imperative-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Call Apex methods imperatively from LWC — on button click, lifecycle hooks, or conditional logic. Covers import syntax, cacheable vs non-cacheable, async/await patterns, error handling, loading states, and Promise.all. NOT for wire service (use wire-service-patterns) and NOT for testing Apex mocks (use lwc-testing).

dataweave-for-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when transforming structured data inside Apex — CSV → JSON, XML → SObject list, JSON → flattened CSV, or schema-mapping a third-party payload to a Salesforce model — and the existing options (`JSON.deserialize`, `Dom.Document`, hand-written loops) are getting unwieldy. Triggers: 'apex transform csv json xml without external library', 'system.dataweave script', 'salesforce native dataweave apex execute', 'transform xml to sobject apex no mulesoft', 'json reshape salesforce apex script'. NOT for MuleSoft Anypoint DataWeave running off-platform (use mulesoft-anypoint-architecture), NOT for Apex JSON serialization basics (use apex-json-serialization), NOT for Bulk API CSV ingest (use bulk-api-2-patterns).

flow-invocable-from-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Author @InvocableMethod Apex classes that Flow can call as Actions. Design the input / output variable contract, bulk semantics (one list in, one list out), null handling, and error surfacing. Also covers the inverse direction: calling a flow from Apex via Flow.Interview. NOT for general Apex authoring (use apex-service-selector-domain). NOT for REST-exposed Apex (use apex-rest-resource-patterns).

flow-email-and-notifications

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when sending emails, in-app bell notifications, SMS, or Slack messages from Salesforce Flow. Trigger keywords: 'send email action', 'custom notification', 'bell icon', 'Send Custom Notification', 'SMS from flow', 'Slack notification flow'. NOT for designing or managing email templates from Setup (use admin/email-templates-and-alerts), and NOT for Email Alerts defined in workflow rules.

flow-apex-defined-types

8
from PranavNagrecha/AwesomeSalesforceSkills

Design and use Apex-Defined Types as Flow variables for structured non-sObject data (HTTP callout payloads, External Service responses, complex configuration). Trigger keywords: apex-defined type, flow variable, @AuraEnabled class, flow http callout response. Does NOT cover building HTTP Callout Actions themselves, External Services schema, or raw Apex invocable methods.

scheduled-apex-failure-detection-and-monitoring

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when nightly batch / scheduled Apex jobs are failing without anyone noticing — covers why uncaught exceptions in `execute()` go to the debug log instead of email, how to query `AsyncApexJob` for `Status`, `NumberOfErrors`, and `ExtendedStatus`, when to implement `Database.RaisesPlatformEvents` so the platform publishes `BatchApexErrorEvent` on uncaught failures, how to subscribe to that event with an Apex trigger and notify operators, and how to layer a custom watcher schedule on top so silent-failure modes (job that never started, scheduled class deleted, queue stuck on `Queued`) still surface. Triggers: 'nightly batch failed at 2am with no notification', 'how do we know if a scheduled apex job is failing', 'BatchApexErrorEvent vs custom retry logic', 'Setup Apex Jobs only shows last 7 days, where else can I look', 'job is stuck in queued status nobody noticed for a week'. NOT for general Apex exception handling patterns (use apex/apex-exception-handling-and-logging), NOT for Batch Apex authoring or chunking strategy (use apex/batch-apex-design), NOT for Setup → Apex Jobs UI walkthrough as an admin task (use admin/batch-job-scheduling-and-monitoring), NOT for retry logic itself (use apex/scheduled-apex-retry-patterns once authored).

platform-events-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when publishing or subscribing to Salesforce Platform Events from Apex, comparing Platform Events with Change Data Capture, or designing event-triggered error handling and monitoring. Triggers: 'EventBus.publish', 'platform event trigger', 'CDC vs Platform Events', 'replay ID', 'high-volume event'. NOT for Flow-only publish/subscribe automation.

health-cloud-apex-extensions

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when extending Health Cloud via Apex: implementing HealthCloudGA managed-package interfaces, automating care plan lifecycle hooks, processing referrals using Industries Common Components invocable actions, or enforcing HIPAA-compliant logging governance for clinical Apex code. Trigger keywords: CarePlanProcessorCallback, HealthCloudGA namespace, ReferralRequest, ReferralResponse, care plan invocable actions, clinical Apex extension, Health Cloud Apex API. NOT for standard Apex triggers or generic Apex development unrelated to Health Cloud managed-package extension points.

fsl-apex-extensions

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when writing Apex that calls Field Service Lightning scheduling APIs — AppointmentBookingService, ScheduleService, GradeSlotsService, or OAAS — to book, schedule, grade, or optimize service appointments programmatically. Trigger keywords: FSL Apex namespace, GetSlots, schedule service appointment via code, appointment booking API, FSL optimization API. NOT for standard Apex patterns unrelated to FSL, admin-level scheduling policy configuration, or declarative FSL scheduling.

fsc-apex-extensions

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when extending Financial Services Cloud (FSC) behavior through Apex: customizing financial rollup recalculation, disabling built-in FSC triggers to write custom trigger logic, implementing Compliant Data Sharing (CDS) participant/role integrations, or building custom FSC action handlers. NOT for standard Apex unrelated to the FSC managed package, standard Salesforce sharing rules, or configuring FSC rollups through the Admin UI — use the admin/financial-account-setup skill for declarative rollup setup.

entitlement-apex-hooks

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when writing Apex triggers or classes that interact with CaseMilestone records — completing milestones, detecting violations, or reacting to SLA state changes. Trigger keywords: CaseMilestone trigger, auto-complete milestone Apex, milestone violation polling, CompletionDate write pattern. NOT for entitlement process admin setup, milestone configuration in Setup UI, or Flow-based milestone actions.