Inbound email processing in Salesforce via Email Services + the `Messaging.InboundEmailHandler` Apex interface. Covers EmailService configuration (running user, accept-from address, attachment handling, error / failure routing), the EmailServicesAddress per-routing-address pattern, the handler's `Messaging.InboundEmail` payload (text body, HTML body, headers, attachments, in-reply-to threading), and the canonical Email-to-Case alternative for case creation. NOT for outbound email (use admin/e...
Scanned 9/6/2026
Install to Claude Code
npx -y skills add PranavNagrecha/AwesomeSalesforceSkills --skill email-service-inbound --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Email Service Inbound?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/pranavnagrecha-email-service-inbound)More formats (shields.io, HTML) on the badges page.
---
name: email-service-inbound
description: "Inbound email processing in Salesforce via Email Services + the `Messaging.InboundEmailHandler` Apex interface. Covers EmailService configuration (running user, accept-from address, attachment handling, error / failure routing), the EmailServicesAddress per-routing-address pattern, the handler's `Messaging.InboundEmail` payload (text body, HTML body, headers, attachments, in-reply-to threading), and the canonical Email-to-Case alternative for case creation. NOT for outbound email (use admin/email-templates-and-alerts), NOT for Email-to-Case flow customization itself (use admin/email-to-case-configuration)."
category: admin
salesforce-version: "Spring '25+"
well-architected-pillars:
- Reliability
- Security
- Operational Excellence
triggers:
- "messaging inboundemailhandler apex class email service"
- "salesforce email service routing address EmailServicesAddress"
- "inbound email parse threading in-reply-to message-id"
- "email service binary attachment max size limit"
- "email service running user authorized senders"
- "email-to-case vs custom email service decision"
tags:
- email-service
- inboundemailhandler
- emailservicesaddress
- inbound-email
- email-to-case
inputs:
- "What the inbound email needs to produce: Case, custom record, file upload, audit log, downstream API call"
- "Sender population: known users, anonymous public, mixed"
- "Volume: emails / day, peak / minute"
- "Attachment handling: discard, attach to record, archive"
- "Threading: standalone emails or part of a conversation"
outputs:
- "Email Service + EmailServicesAddress configuration"
- "Apex class implementing Messaging.InboundEmailHandler"
- "Routing-address policy (accept-from, max retention, error response)"
- "Decision: custom email service vs Email-to-Case"
dependencies: []
version: 1.0.0
author: Pranav Nagrecha
updated: 2026-05-05
---
# Inbound Email Service
Salesforce can accept inbound email and run Apex against each
message. The mechanism: an **Email Service** (org-level
configuration) maps one or more **Email Service Addresses** (the
local-part + Salesforce-supplied domain) to a class implementing
`Messaging.InboundEmailHandler`. Apex receives the parsed email,
returns a `Messaging.InboundEmailResult`, and the platform
responds (delivery success, error reply, drop) accordingly.
The classic alternative is **Email-to-Case** — a built-in service
that creates a Case from each email, with extensive admin
configuration (auto-response, routing, threading, contact lookup).
Email-to-Case is the right answer for case creation; custom email
service is the right answer for everything else (file uploads,
custom-object creation, audit logging, complex routing).
What this skill is NOT. Outbound email — `admin/email-templates-and-alerts`.
Email-to-Case-specific configuration — `service/email-to-case`.
This skill is the custom-handler path.
---
## Before Starting
- **Decide custom service vs Email-to-Case.** Case creation? E2C
is built; don't reinvent. Anything else? Custom service.
- **Plan the running user.** The handler runs as the user
configured on the Email Service. Their permissions determine
what the handler can do. Use a dedicated integration user.
- **Plan the accept-from policy.** Anonymous public addresses
receive spam; consider authorized-senders allow-listing or
rate limiting.
- **Plan attachment handling.** Salesforce has limits (max email
size, max attachment size, total org file storage). Decide
store vs discard before traffic ramps.
---
## Core Concepts
### Email Service vs Email Services Address
- **Email Service** (org-level) — the configuration: running
user, max retention, accept-from policy, accept attachments,
error response template.
- **Email Services Address** (per address) — the actual local-part
+ Salesforce-supplied subdomain. One Email Service can have
many Addresses (e.g. `quotes-prod@...`, `quotes-dev@...`,
`quotes-eu@...`), all routing to the same handler.
The handler doesn't see which Address received the email
*directly* — it sees the recipient in `email.toAddresses`. Branch
on the recipient if you need per-address logic.
### `Messaging.InboundEmailHandler` interface
```apex
global class IncomingQuoteHandler implements Messaging.InboundEmailHandler {
global Messaging.InboundEmailResult handleInboundEmail(
Messaging.InboundEmail email,
Messaging.InboundEnvelope envelope
) {
Messaging.InboundEmailResult result = new Messaging.InboundEmailResult();
try {
processQuote(email);
result.success = true;
} catch (Exception ex) {
result.success = false;
result.message = 'Could not process: ' + ex.getMessage();
ApplicationLogger.error('Quote email failed', ex);
}
return result;
}
}
```
Three things to know:
1. **`global` is required.** Same as `SandboxPostCopy`.
2. **`InboundEmail` payload** — `subject`, `fromAddress`,
`fromName`, `toAddresses`, `ccAddresses`, `plainTextBody`,
`htmlBody`, `headers` (a list, not a map — the `references` /
`inReplyTo` headers are critical for threading), `binaryAttachments`,
`textAttachments`.
3. **Return value.** `success = true` → delivery confirmed.
`success = false` + `message` → bounce-back response. The
`message` is what the sender sees.
### Email threading via `In-Reply-To` and `References` headers
Email clients thread replies by `Message-Id` and `In-Reply-To`
headers. Salesforce's email service parses them in `email.headers`:
```apex
String inReplyTo = null;
for (Messaging.InboundEmail.Header h : email.headers) {
if (h.name.toLowerCase() == 'in-reply-to') {
inReplyTo = h.value;
break;
}
}
```
For threading inbound emails to existing Salesforce records:
- **Email-to-Case threading** — uses `[ref:...]` token in subject /
body. The system inserts the token in outbound replies; inbound
replies preserve it; E2C extracts it and links the email to the
existing case.
- **Custom service threading** — implement your own. Either embed
a token in your outbound emails (case-insensitive, robust against
client mangling) or look up by `In-Reply-To` against a stored
Message-Id of your previous outbound.
### Attachment handling
```apex
for (Messaging.InboundEmail.BinaryAttachment att : email.binaryAttachments) {
ContentVersion cv = new ContentVersion(
Title = att.fileName,
PathOnClient = att.fileName,
VersionData = att.body,
FirstPublishLocationId = parentRecordId
);
insert cv;
}
```
Limits:
- **Max email size**: ~25 MB total (configured per Email Service;
email-with-attachments above this are bounced).
- **Max single attachment**: limited by the email-size cap.
- **Org-wide file storage**: every saved attachment counts against
the org's File Storage allocation. Plan retention.
### Email-to-Case vs custom service decision
| Need | Use |
|---|---|
| Create a Case from an email | **Email-to-Case** |
| Auto-response from a template | **Email-to-Case** (or On-Demand E2C) |
| Threading replies to existing Case | **Email-to-Case** with `[ref:...]` token |
| Create a Lead / Opportunity / Custom Object | **Custom service** |
| Upload a file to a record | **Custom service** |
| Trigger a downstream API callout | **Custom service** |
| Complex routing (e.g. "if subject starts with X, do Y") | **Custom service** (or E2C with assignment rules — depends) |
| Multi-language / encoded payloads | **Custom service** for full control |
---
## Common Patterns
### Pattern A — Lead-from-email-form
**When to use.** Marketing landing page submits a form via email
to a known address; need to create a Lead from each.
```apex
global class LeadFromEmail implements Messaging.InboundEmailHandler {
global Messaging.InboundEmailResult handleInboundEmail(
Messaging.InboundEmail email, Messaging.InboundEnvelope envelope
) {
Messaging.InboundEmailResult res = new Messaging.InboundEmailResult();
try {
Lead l = new Lead(
Email = email.fromAddress,
LastName = email.fromName != null ? email.fromName : '(unknown)',
Company = parseCompanyFromBody(email.plainTextBody),
LeadSource = 'Email Form'
);
insert l;
res.success = true;
} catch (DmlException ex) {
res.success = false;
res.message = 'Could not create lead: ' + ex.getMessage();
}
return res;
}
}
```
### Pattern B — File upload to existing record via subject token
**When to use.** Users email attachments to a routing address with
the record Id in the subject (`Upload — 0061a000007ABC`).
The handler parses the Id from the subject, validates it, attaches
files. Returns success / failure to the sender.
### Pattern C — Anti-spam allow-list
**When to use.** Public-facing routing address that gets spam.
```apex
private static final Set<String> ALLOWED_DOMAINS = new Set<String>{
'acme.com', 'partner.example.com'
};
global Messaging.InboundEmailResult handleInboundEmail(
Messaging.InboundEmail email, Messaging.InboundEnvelope envelope
) {
String fromDomain = email.fromAddress.substringAfter('@').toLowerCase();
if (!ALLOWED_DOMAINS.contains(fromDomain)) {
Messaging.InboundEmailResult res = new Messaging.InboundEmailResult();
res.success = false;
res.message = 'Sender domain not authorized';
return res;
}
// ... legitimate processing ...
}
```
For more nuanced allow-listing, store the list in Custom Metadata
or Custom Setting so admins can manage without redeploying Apex.
---
## Decision Guidance
| Situation | Approach | Reason |
|---|---|---|
| Create a Case from email | **Email-to-Case** | Built-in; threading, routing, auto-response included |
| Create any other record from email | **Custom Email Service + InboundEmailHandler** | E2C only creates Cases |
| Public address receives spam | **Custom service with allow-list** | E2C also has spam handling but per-Case |
| Need to upload files | **Custom service** | E2C Email Message attachments tied to Case |
| Threading replies to existing record | **Subject token** (`[ref:...]`) or `In-Reply-To` header parsing | Email clients mangle these; tokens are more robust |
| Inbound volume > 1K / day per address | **Plan governor budget** + dedicated running user | High volume can hit Apex governor in a single batch |
| Multi-language / RTL / encoded subject | **Custom handler with explicit charset handling** | E2C parses for the common cases; edge cases break it |
| Inbound email triggers a callout | **Custom service** + Platform Event for async work | Don't do the callout in the handler synchronously |
| Anonymous public access undesirable | **Email Service `Authorize Email Addresses`** | Per-Service allow-list at the platform level |
---
## Recommended Workflow
1. **Decide custom service vs Email-to-Case.** Case creation → E2C; everything else → custom.
2. **Provision a dedicated running user** for the Email Service. Document its required permissions.
3. **Implement the handler.**
- Write `Messaging.InboundEmailHandler` with the right business logic.
- Plan threading if applicable (subject token, `In-Reply-To` parse).
4. **Configure the Email Service and its addresses.**
- Setup → Email → Email Services. Set running user, max email size, accept attachments, error-response template.
- Create one or more Email Services Addresses. Each gets a Salesforce-supplied subdomain.
5. **Test and monitor.**
- Send real emails to the address. Verify success / failure paths.
- Monitor inbound email volume, handler exceptions, attachment storage growth.
---
## Review Checklist
- [ ] Class implements `Messaging.InboundEmailHandler` with `global` access.
- [ ] Handler returns `InboundEmailResult` with explicit `success` value (never silently throws).
- [ ] Running user is dedicated to the Email Service, with documented permissions.
- [ ] Allow-list / spam handling for public-facing addresses.
- [ ] Attachment retention policy explicit.
- [ ] Threading via subject token or `In-Reply-To` if applicable.
- [ ] Test class covers success path, malformed input, attachment-too-large, allow-list-rejection.
- [ ] Email Service `Max Email Size` and `Accept Attachments` configured deliberately.
---
## Salesforce-Specific Gotchas
1. **`Messaging.InboundEmailHandler` requires `global` access.** Same as `SandboxPostCopy`. (See `references/gotchas.md` § 1.)
2. **`email.headers` is a `List<Header>`, not a `Map`.** Iterate to find the header you want. (See `references/gotchas.md` § 2.)
3. **Returning `success = false`** triggers a bounce-back to the sender. The `message` is what the sender sees — don't expose stack traces. (See `references/gotchas.md` § 3.)
4. **Max email size has a hard ceiling**; emails above are bounced before the handler sees them. (See `references/gotchas.md` § 4.)
5. **Email Services Address subdomain is Salesforce-supplied**, not customer-domain-mappable. For customer domains, use email forwarding from the customer side. (See `references/gotchas.md` § 5.)
6. **The handler runs in the configured running user's context**, not the sender's. FLS / sharing applies to that user. (See `references/gotchas.md` § 6.)
7. **In-Reply-To / Message-Id headers are unreliable** across email clients; subject-token threading is more robust. (See `references/gotchas.md` § 7.)
---
## Output Artifacts
| Artifact | Description |
|---|---|
| `Messaging.InboundEmailHandler` class | The handler implementation |
| Test class | Covers handler with synthetic `InboundEmail` payloads |
| Email Service configuration | Documented setup steps in Setup → Email → Email Services |
| Allow-list source | Custom Metadata / Custom Setting for admin-managed sender list |
| Threading strategy | Subject token format or `In-Reply-To` parsing logic |
---
## Related Skills
- `service/email-to-case` — when the requirement is case creation; this skill is the custom-service alternative.
- `admin/email-templates-and-alerts` — outbound email infrastructure.
- `apex/apex-event-bus-subscriber` — when the handler publishes a Platform Event for async downstream work.
- `apex/dynamic-apex` — when the handler needs Schema describe to create records of varying types.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!