People search for an Apex sharing rule for EmailMessage because they want to open up, or lock down, who can read emails stored in Salesforce. The short answer is that you cannot write one. EmailMessage does not have its own sharing model. Who can see an email is decided by the records it is attached to, and Apex that runs without sharing ignores that decision entirely.
That matters because an EmailMessage record holds the full body, the addresses and often the headers. A case thread can contain anything a customer typed into a reply.
This post covers how EmailMessage access is decided, what you cannot do, what changes when Apex queries it, the guest and community angle, and how to check your own org.
How is EmailMessage visibility decided?
EmailMessage records do not sit in the Sharing Settings list with their own organization-wide default, and there is no EmailMessageShare object. Access is inherited from related records instead.
What we can say with confidence:
- Email-to-Case and case emails. An email whose
ParentIdis a Case is visible to users who can see that Case. If a user loses access to the case, they lose access to its emails. - Enhanced Email. With Enhanced Email turned on, emails become first-class records that can relate to other objects through
RelatedToIdand to people through the email's relation records. Visibility then follows those relationships and the associated activity, not a separate rule on the email itself.
What depends on your org:
- Whether Enhanced Email is enabled, and how each email was created (Email-to-Case, Lightning email composer, an integration, Apex
Messagingwith activity logging on or off). - The sharing model of the parent or related object: a private Case model behaves very differently from a public read/write one.
- Draft handling and any customizations your org layers on top.
Salesforce documents the exact access rules in its Enhanced Email considerations. Read them against your own settings rather than relying on a summary, including this one.
Can you write a sharing rule or Apex managed share for EmailMessage?
No. Sharing rules and Apex managed sharing both work through an object's sharing model and its Share object. EmailMessage has neither, so:
- There is no criteria-based or owner-based sharing rule you can define on EmailMessage.
- You cannot insert an
EmailMessageSharerow from Apex, because the object does not exist.
If a group of users needs to read a set of emails, share the parent instead: a Case sharing rule, Case team, queue membership, or Apex managed sharing on CaseShare or the custom object's share table. If a set of emails must be hidden from people who can see the parent, sharing cannot do that. You need a different design, such as keeping the sensitive content in a separate object with its own private model.
What happens when Apex queries EmailMessage without sharing?
Because visibility comes from the parent, the Apex sharing keyword decides everything. A class running without sharing returns every EmailMessage that matches the filter, whatever the running user can see on the Case side.
public with sharing class CaseEmailController {
@AuraEnabled
public static List<EmailMessage> thread(Id caseId) {
return CaseEmailQueries.forCase(caseId); // runs without sharing
}
}
public without sharing class CaseEmailQueries {
public static List<EmailMessage> forCase(Id caseId) {
return [SELECT Id, Subject, FromAddress, ToAddress, HtmlBody
FROM EmailMessage
WHERE ParentId = :caseId];
}
}The controller is with sharing, so it looks fine in review. The helper is not. Anyone who can call thread and supply a case id reads that case's full email thread, including cases they have no access to. Case ids are not secret: they appear in URLs, emails and exports.
The fix is the same as for any record: keep the query in user mode, and do not let the caller pick the record a bypass reads.
public with sharing class CaseEmailQueries {
public static List<EmailMessage> forCase(Id caseId) {
return [SELECT Id, Subject, FromAddress, ToAddress, HtmlBody
FROM EmailMessage
WHERE ParentId = :caseId
WITH USER_MODE];
}
}WITH USER_MODE applies the running user's sharing as well as object and field permissions, so the query returns only emails on cases the user can open. We covered the keyword rules, including what an omitted keyword means before and after API 67.0, in with sharing vs without sharing in Apex.
The same pattern hides in triggers that copy email bodies to other objects, async jobs that export emails, and invocable methods that pass email content back to a flow screen.
Guest and community users
Experience Cloud is where this turns from an internal leak into an external one. Secure guest record access means a guest sees no Case unless a guest sharing rule grants it, and an authenticated community user sees only the cases their sharing allows. Both protections are on the Case. Neither reaches the email if Apex skips sharing.
A without sharing class granted to the guest profile or a community profile, with an @AuraEnabled method that returns EmailMessage rows by a caller-supplied id, gives the visitor the email thread of any case whose id they can find. The guest walked past the Case sharing model without ever touching it. We traced this shape in the anatomy of a guest user data leak.
Broad Case sharing rules matter here too: a rule that shares cases to a wide group shares every email on those cases. That is the theme of the sharing rule that shares too much.
How to check EmailMessage access in your org
- List the Apex that touches EmailMessage. Search for
FROM EmailMessage,EmailMessagein DML andDatabase.querystrings. For each class, note its sharing keyword and API version. - Find who can call each one. Which
@AuraEnabled,@RestResource,@InvocableMethodand Visualforce entry points reach it, and which profiles and permission sets grant those classes. - Check where the record id comes from. A filter on a caller-supplied
caseIdorParentIdpluswithout sharingis an insecure direct object reference. - Test as the user. Sign in as a representative internal, community and guest user, or write an Apex test with
System.runAs, and confirm what the query returns for a case that user cannot see. - Review the parent's sharing. Case organization-wide defaults, Case sharing rules, guest sharing rules and queues decide email visibility, so review them as email controls.
What Vulkro for Salesforce reports
Vulkro for Salesforce has no special rule for EmailMessage, and does not need one: the issue is the general one of Apex reading records past the caller's sharing. vulkro-sf scan works out whether each class runs in user mode or system mode with or without sharing, following the call chain. apex-xmethod-sharing-bypass-via-without-sharing-helper reports a with sharing entry point that delegates to a without sharing helper which queries records, like the CaseEmailController example. SF-GUEST-AURA-001 reports guest-granted @AuraEnabled methods that do DML or dynamic SOQL with no authorization check. To list what an unauthenticated visitor can reach, with proof, run vulkro-sf exposure.
vulkro-sf scan ./force-app
vulkro-sf exposure ./force-appWhen a guest or community profile can reach the helper, the finding becomes part of an attack path from that user to the object, with each step cited to a file and line.
FAQ
Can I create a sharing rule on EmailMessage in Salesforce?
No. EmailMessage has no sharing model of its own and does not appear in Sharing Settings. Share the parent record, usually the Case, and the emails follow it.
Is there an EmailMessageShare object for Apex managed sharing?
No. Apex managed sharing needs a share object, and EmailMessage has none. Use Apex managed sharing on the parent, such as CaseShare or a custom object's share table.
Does with sharing protect EmailMessage queries in Apex?
Yes, for record visibility: with sharing makes the query respect the running user's access to the related records. It does not enforce field permissions, so use WITH USER_MODE to cover both.
Run the same checks on your own org with Vulkro for Salesforce, or start a 14-day trial from /start/.