Salesforce sandbox email masking matters because deliverability settings alone don't stop test traffic from reaching real inboxes. Admins who rely on the built-in "System email only" option often assume it blocks every outbound message, but flows, Apex callouts, and third-party integrations routinely bypass it. If the email field still holds a real customer address, something in your sandbox will eventually find a way to send to it.
I've seen this play out at a mid-size insurance client: a QA tester ran a batch Apex job against a partial copy sandbox, triggering 4,000 renewal notices to live policyholders. Deliverability settings were configured correctly. The email field just wasn't masked. That single gap turned a routine test into a GDPR incident report and an uncomfortable call with legal.
Why Deliverability Settings Aren't a Masking Substitute
Salesforce's deliverability setting, found under Email Administration, lets you choose between "No access," "System email only," and "All email." Most orgs set sandboxes to "System email only" believing this caps all outbound mail. It doesn't. It restricts emails sent through Salesforce's native email features like templates and workflow email alerts, but it does nothing for Apex-initiated HTTP callouts to third-party email services.
Marketing Cloud, Pardot, SendGrid integrations, and custom REST callouts all route around the deliverability setting entirely. If your org sends transactional email through an external service and that integration stays active in sandbox, the deliverability dropdown is irrelevant. The address in the field is what gets used, masked or not.
There's a second blind spot: scheduled Apex and batch jobs that were already queued before a refresh. A sandbox refresh doesn't clear scheduled jobs tied to Apex classes with specific triggers. If those jobs fire against unmasked data before your masking tool runs its first pass, the window for an accidental send is wide open.
Where Email Addresses Hide Beyond the Obvious Fields
Most masking tools cover Contact.Email and Lead.Email without much trouble. The harder problem is everywhere else an address gets copied, formatted, or stored as text instead of the standard Email field type.
- Custom fields on Account, Opportunity, or custom objects storing secondary or billing contact emails
- Long text area fields where support agents paste a customer's email into free-form notes
- Chatter posts and feed items referencing a contact by email instead of a record link
- Email-to-Case generated cases where the sender address lives in the From Address field of EmailMessage records
- Marketing Cloud data extensions synced via Marketing Cloud Connect, which hold their own copies of subscriber emails outside the core Salesforce object model
EmailMessage records deserve particular attention. Every inbound and outbound email logged through Email-to-Case or Activities creates a record with sender, recipient, and CC fields populated with real addresses. Standard field-level masking tools that only target Contact and Lead objects walk right past thousands of these records sitting in a sandbox, fully searchable, fully real.
The Difference Between Hiding an Address and Neutralizing It
Replacing a real email with a random string isn't the same as making it safe. Good masking has to satisfy two separate requirements at once: the address has to look plausible enough that validation rules, duplicate rules, and Apex logic depending on email format still function, and it has to be structurally incapable of reaching a real mailbox.
The second requirement is the one teams skip. A masked address like jane.smith@fakecorp.com looks fine but will bounce or, worse, land in someone's actual inbox if fakecorp.com happens to be a registered domain. The safer pattern uses a domain Salesforce itself blocks from delivery, or a domain your org controls and has configured with no MX records, so any stray send simply fails instead of landing somewhere unexpected.
| Approach | Looks Valid | Guarantees No Delivery |
|---|---|---|
| Random string + real-looking domain (e.g. gmail.com) | Yes | No |
| Masked name + org-owned dead domain (no MX record) | Yes | Yes |
| Deliverability setting set to No access only | N/A | Partial — doesn't stop external callouts |
| Deterministic fake email tied to record ID | Yes | Yes, if domain is dead |
Deterministic masking, where the same source record always produces the same fake email, matters more than people expect. QA teams often need to search for a specific test contact across multiple test cycles. If masking regenerates a random address on every refresh, testers lose the ability to track a record by email across sandbox rebuilds, and they start filing tickets claiming the masking tool "broke" their test data. It didn't break anything. It just wasn't deterministic.
Email Fields Inside Flows, Templates, and Formula Logic
Masking an Email field type is mechanically simple. The complications start when that address gets referenced somewhere else in the metadata layer. A formula field concatenating First Name and Last Name into an email-style string for an integration key won't get touched by field-level masking unless the tool also understands formula dependencies.
Flows present a similar problem. A Screen Flow that pre-populates a "Confirm Email" field by querying the Contact record will happily pull whatever value sits there, masked or not. If the flow then fires an outbound message or a Platform Event that an external listener picks up, you've recreated the same leak the deliverability setting was supposed to prevent, just through a different door.
Email templates stored as classic templates or Lightning Email Templates sometimes contain hardcoded test addresses left over from earlier debugging sessions. These aren't PII from a masking standpoint, but they're worth auditing during the same cycle, since a forgotten personal email in a template's CC field is its own kind of exposure.
Building a Masking Pass That Actually Covers Email
A complete approach treats email as a cross-object concern rather than a single field type. That means mapping every object and field that can hold an address format string, not just the ones labeled Email in the schema.
Start with a full-org field scan rather than trusting standard object lists. Custom objects accumulate email-shaped text fields over years of org evolution, often added by a consultant who needed a quick place to store a secondary contact and never flagged it as sensitive. Automated PII discovery catches these faster than manual review, and it catches them before a refresh, not after a breach.
Next, standardize on a single dead domain across every masked address in the org, and register that domain internally so nobody accidentally points real MX records at it later. Document which domain is reserved for masking so new admins don't reuse it for something else. Finally, pair field masking with a review of active outbound integrations, disabling or redirecting Marketing Cloud, Pardot, and custom callout endpoints in every sandbox before the first test run, not after someone reports a problem.
MaskEzee handles this by masking email addresses across standard fields, custom fields, EmailMessage records, and formula dependencies in a single pass tied to CloudEzee's refresh automation, using a fixed dead domain so every masked address fails delivery by design rather than by luck. That removes the guesswork admins currently handle manually, field by field, integration by integration.
What to Check Before Your Next Refresh
Run a query against EmailMessage, Contact, Lead, and any custom objects with email-pattern fields immediately after a refresh completes, before any scheduled jobs or integration users have a chance to run. Confirm every address resolves to your dead domain, not a guessable pattern on a live one.
Review active Named Credentials and external email service connections in the sandbox and disable anything pointing at a production endpoint. Then confirm your deliverability setting is still set to No access or System email only as a second layer, understanding clearly that it's a backstop, not the primary control.
None of this is exotic work. It's tedious, which is exactly why it gets skipped under refresh deadlines, and exactly why a masking tool that automates the full scope of email exposure earns its keep the first time it prevents a test batch job from emailing four thousand real customers.
Frequently Asked Questions
Does Salesforce's deliverability setting prevent masked sandboxes from sending real emails?
No, not by itself. The deliverability setting only restricts Salesforce's native email features like templates and workflow alerts. It does nothing to stop Apex callouts to external services like Marketing Cloud, SendGrid, or custom REST integrations, which is why masking the actual email field values is still required.
What is the safest format for a masked email address in a Salesforce sandbox?
Use a realistic-looking address on a domain your organization controls and has deliberately left without MX records. This way the format passes validation rules and duplicate logic, but any accidental send fails immediately instead of reaching a live inbox or an unintended third party.
Why does deterministic email masking matter for QA testing?
Deterministic masking produces the same fake address for the same source record every time a sandbox refreshes. This lets QA teams track a specific test contact across multiple refresh cycles, while random regeneration on every refresh breaks that continuity and makes regression testing harder to manage.
Which Salesforce objects besides Contact and Lead commonly hold unmasked email addresses?
EmailMessage records from Email-to-Case, custom long text area fields used for notes, custom fields storing secondary or billing contacts, and Marketing Cloud data extensions synced through Marketing Cloud Connect are all common places email addresses hide outside the standard Email field type.
Can a formula field expose a real email address even after masking runs?
Yes, if the formula concatenates name or identifier fields into an email-style string for integration purposes and the masking tool doesn't recalculate or account for formula dependencies. Masking needs to address both the source fields and any derived logic referencing them to close this gap completely.