SPF, DKIM, and DMARC so your messages don't end up in the spam folder. Use built-in Salesforce email for one-off alerts tied to a specific customer record, and use a dedicated marketing platform for massive promotional blasts.
Messaging.SingleEmailMessage), a standard email template, and your verified support@yourcompany.com Org-Wide Address. Now, what if your marketing team wants to blast 300,000 Black Friday coupons to every lead in your database? Do not use core Salesforce or Apex for this! Daily governor limits exist specifically to keep bulk marketing blasts out of your core CRM and push them into dedicated tools like Marketing Cloud or Account Engagement (formerly Pardot).
🎯 Key Points: The Pillars of Salesforce Email
Salesforce email architecture splits cleanly into two paths: sending emails out (outbound) and receiving emails in (inbound). Knowing where each tool shines—and where it breaks—is the secret to building a stable, scalable Salesforce org.
- 📤 Outbound Architecture: Handled by declarative Flow Email Alerts (
Send Emailaction), custom Apex code (Messaging.SingleEmailMessage), and Org-Wide Email Addresses so messages come from a shared team inbox rather than an individual user. - 📥 Inbound Architecture: Handled out-of-the-box by Email-to-Case (utilizing modern Lightning Threading via headers and tokens rather than the retired
Ref IDstandard) or custom Apex Email Services using theMessaging.InboundEmailHandlerinterface when you need to parse complex emails via code. - 🛡️ Deliverability & Security Standards: Reaching the inbox requires strict compliance with modern email security protocols:
SPF,DKIM, andDMARC. Since Google, Yahoo, and Microsoft enforced strict bulk-sender rules, failing to configure these will guarantee your Salesforce emails get blocked or sent straight to spam. - 🚧 Strict Governor Limits: Salesforce caps external email sends at 5,000 emails per day per org (sent via Apex or APIs to external email addresses) and allows only 10
sendEmail()method calls per Apex transaction. - 🆓 The Internal User Loophole: Sending emails to your own internal active
Userrecords in Salesforce does not count against your 5,000 daily external email limit—provided you pass the User's ID intosetTargetObjectId()instead of hardcoding their email address as a string!
🧭 The 360 Card: Email Architecture & Deliverability
📏 The Golden Rule: Keep transactional, record-triggered notifications inside core Salesforce. Offload newsletters, cold outreach, and high-volume promotional blasts to Marketing Cloud or Account Engagement.
🎁 The Gain: Using native Salesforce email for transactional alerts gives you instant access to Lightning Email Templates, Org-Wide Addresses, automatic Activity Timeline logging, and live CRM merge fields without paying for extra middleware.
💸 The Price: Your company's domain reputation is on the line. If a badly written Flow or Apex batch job accidentally spams invalid addresses, email providers like Gmail can blacklist your entire corporate domain.
🛑 The Limits: A hard limit of 5,000 external emails per 24-hour GMT window via Apex/API, and 10 Messaging.sendEmail() calls per transaction. Always collect your email objects in a single List and invoke Messaging.sendEmail() once outside your loop.
💬 Core Q&A & Interview Prep
Q1: You need to send a templated confirmation email from Apex using a shared support address while respecting governor limits. How do you build this?
Messaging.SingleEmailMessage paired with a pre-queried Org-Wide Email Address and a Lightning Email Template. To respect governor limits, I would bulkify the logic by adding every message to a collection, passing Contact or User IDs into setTargetObjectId(), and calling Messaging.sendEmail() once outside the for loop with partial success enabled."
To write production-ready email code in Apex, never hardcode the subject or body inside your class. Instead, query the EmailTemplate and OrgWideEmailAddress records before your loop starts. Inside your loop, create a Messaging.SingleEmailMessage instance for each record.
Use setTemplateId() so admins can update the email text anytime without needing a code deployment. Use setOrgWideEmailAddressId() so the customer sees a trusted address like support@yourcompany.com instead of the personal email of whichever user clicked the button.
Next, pass the recipient's Contact, Lead, or User ID into setTargetObjectId() and pass the related Case or Opportunity ID into setWhatId() so Salesforce can populate merge fields like {{{Case.CaseNumber}}}. Finally, pass false as the second parameter in Messaging.sendEmail(msgs, false). That allOrNone = false flag ensures that if one customer has a bad email address, the other 199 emails in the batch still go out!
// 1. Query Template and Org-Wide Email Address ONCE outside the loop
Id templateId = [SELECT Id FROM EmailTemplate WHERE DeveloperName = 'Case_Closed_Survey' LIMIT 1].Id;
Id owaId = [SELECT Id FROM OrgWideEmailAddress WHERE Address = 'support@company.com' LIMIT 1].Id;
// 2. Create a single list to hold all outgoing emails
List<Messaging.SingleEmailMessage> msgs = new List<Messaging.SingleEmailMessage>();
for (Case c : closedCases) {
if (c.ContactId == null) continue; // Guard against Cases without a Contact
Messaging.SingleEmailMessage m = new Messaging.SingleEmailMessage();
// Pass Contact/Lead/User ID (never raw email strings if you can avoid it)
m.setTargetObjectId(c.ContactId);
// Link template and the Case record for merge fields
m.setTemplateId(templateId);
m.setWhatId(c.Id);
// Use the verified shared support address
m.setOrgWideEmailAddressId(owaId);
// Log the email as an activity on the record timeline
m.setSaveAsActivity(true);
msgs.add(m);
}
// 3. ONE bulk call outside the loop, with allOrNone set to FALSE for partial success
if (!msgs.isEmpty()) {
Messaging.SendEmailResult[] results = Messaging.sendEmail(msgs, false);
for (Messaging.SendEmailResult res : results) {
if (!res.isSuccess()) {
System.debug('Email failed to send: ' + res.getErrors()[0].getMessage());
}
}
}
Q2: Emails from your Salesforce org are landing in customers' spam folders or getting rejected. What deliverability settings do you check to fix this?
SPF, DKIM, and DMARC alignment. Second, I would check Salesforce's internal Deliverability settings, Email Security Compliance checkboxes, and Bounce Management configuration."
Email providers like Gmail, Yahoo, and Microsoft Office 365 enforce strict authentication rules. If your org's emails are hitting spam, walk through this exact checklist:
- 🔍 SPF (Sender Policy Framework): Check your company's public DNS TXT record. Does it include
include:_spf.salesforce.com? This tells the world that Salesforce servers have permission to send emails on behalf of your domain. - 🔑 DKIM (DomainKeys Identified Mail): Go to Setup > DKIM Keys in Salesforce. You should generate a cryptographic key pair and publish the
CNAMErecords in your company's DNS. DKIM adds a tamper-proof digital signature to every email headers so inbox providers know the email wasn't forged in transit. - 🛡️ DMARC (Domain-based Message Authentication, Reporting, and Conformance): Ensure your domain has a DMARC policy published in DNS and that your
Fromaddress domain aligns with your SPF and DKIM domains. Without DMARC alignment, modern inbox filters will quarantine your messages. - ⚙️ Salesforce Deliverability Settings: Navigate to Setup > Deliverability. Confirm that Access to Send Email is set to All email in Production (note that newly refreshed Sandboxes default to System email only so you don't accidentally spam real customers during testing).
- ⚠️ Sender ID Compliance Warning: Under Setup > Deliverability, watch out for the setting called "Enable compliance with standard email security mechanisms." While it helps pass legacy SPF checks by changing the envelope sender to a
bounces.salesforce.comaddress, turning it on without setting up DKIM keys first can break strict DMARC alignment! Always activate DKIM keys as your primary authentication method. - 📉 Bounce Management: Turn on Activate bounce management in Deliverability settings. Repeatedly emailing dead or invalid addresses ruins your domain reputation. Bounce management automatically flags bad email addresses on the Contact or Lead record so you stop emailing them.
Q3: Design an inbound email process that creates records and handles file attachments. Should you use declarative tools or custom Apex?
Messaging.InboundEmailHandler if we need to create custom object records, parse structured text or JSON from the email body, or run complex validation rules that Flows cannot handle."
When designing inbound email routing, follow the standard Salesforce rule: go declarative first, and drop into code only when required.
If the requirement is routing support inquiries, use On-Demand Email-to-Case. It natively converts incoming emails into Case records, saves attachments directly as Salesforce Files (ContentVersion), and links replies to existing Cases using Lightning Threading (which relies on secure email header data and hidden tokens, replacing the legacy Ref ID format). Plus, with Record-Triggered Flows on the EmailMessage object, you can build powerful declarative automation around incoming emails without touching Apex.
However, if an external vendor emails your org automated XML/CSV reports every night, or if you need to create a custom Invoice__c record by scraping text out of the email body using regular expressions (Regex), use a custom Apex Email Service. You will write an Apex class implementing the Messaging.InboundEmailHandler interface. This gives you full access to email.plainTextBody, email.htmlBody, email.binaryAttachments, and email.textAttachments. Always enforce with sharing or check Field-Level Security (WITH USER_MODE) in your handler class so external senders can't exploit your database permissions.
Q4: Why did my Apex email count against the 5,000 daily external governor limit when I only emailed an internal company manager?
This is a classic Salesforce gotcha! If you pass an email address string into setToAddresses(new List<String>{'manager@company.com'}), Salesforce treats it as an external email and deducts it from your 5,000 daily org limit—even if that email belongs to an active Salesforce User in your org.
To send unlimited emails to internal Salesforce users without touching your daily governor limit, pass the User's 18-character Salesforce record ID directly into setTargetObjectId(userRecordId) and set setSaveAsActivity(false) (since Users cannot have activities logged directly under them like Contacts can). Salesforce immediately recognizes the User ID and processes the email for free!
Q5: When should you use a Flow "Send Email" action instead of writing Apex Messaging.SingleEmailMessage?
For 90% of standard business notifications, you should use the declarative Send Email action inside a Record-Triggered or Scheduled Flow. Modern Salesforce Flows support Lightning Email Templates, Org-Wide Email Addresses, rich text bodies, recipient collections, file attachments, and automatic activity logging on records—features that previously required Apex code.
You only need to reach for Apex Messaging.SingleEmailMessage when you have to generate dynamic PDF attachments on the fly (using Messaging.EmailFileAttachment), construct complex custom MIME headers, handle high-volume asynchronous chunks inside Database.Batchable, or programmatically inspect partial failure results using Messaging.SendEmailResult.
🔗 Connecting the Dots: Follow-Up
Q6: How does Salesforce "Email Relay" fit into this architecture if your corporate IT security team demands that all outbound emails pass through your company's Microsoft 365 or Google Workspace servers?
Salesforce Email Relay acts as a secure bridge between Salesforce and your corporate mail server. Normally, Salesforce sends emails directly from its own servers to your customer's inbox. However, industries like banking and healthcare often require every single outbound email to be scanned for data leaks (DLP), encrypted, or archived in the company's central email vault.
When you configure Email Relay in Setup, Salesforce stops delivering mail directly to recipients. Instead, it opens a secure, TLS-encrypted connection and hands all outbound emails straight to your company's Microsoft Exchange, Office 365, or Google Workspace SMTP relay server. Your corporate mail server runs its own security filters, stamps your company's native DKIM signature on the message, and delivers it to the customer. This keeps your IT security team happy and guarantees that Salesforce emails come from the exact same IP infrastructure as your employees' regular emails.
Q7: Now that you know how outbound and inbound emails work, how do you test your email logic in a Sandbox without accidentally emailing real customers?
When you refresh a Sandbox, Salesforce automatically sets Setup > Deliverability to "System email only" as a safety net, and appends .invalid to the end of every Contact and User email address (for example, john@doe.com.invalid). Before you switch Deliverability to "All email" in a Sandbox to test your Flows or Apex code, always run an anonymous Apex script or Data Loader batch to verify that real customer Contact and Lead email fields are masked, and only remove the .invalid suffix from the specific test records owned by your QA team!
Comments
Questions, corrections and your own experience are all welcome.
Post a Comment