
Native Apex integrations for email validation, sending, campaigns, deliverability monitoring, and content classification. No middleware required. 35+ production-ready patterns with Named Credential authentication.
MailOdds is an email validation, sending, and deliverability API. Connect it to Salesforce to validate lead emails, send transactional messages, monitor sender reputation, and protect revenue.
Sign up at signup.mailodds.com/register. The free tier includes 1,000 validations per month.
Go to Dashboard > Settings > API Keys. Copy the key starting with mo_live_.
A Named Credential is Salesforce's secure way to store API URLs and authentication. Configure it once, then every Apex class references it as callout:MailOdds. See the detailed setup steps below.
Start with the Lead Intake Gate pattern. It validates emails on lead creation and auto-corrects typos. Copy the Apex code, deploy it, and create a trigger to call it.
All integrations below use a single Named Credential. Configure it once, then reference it as callout:MailOdds in every Apex class.
Setup > Named Credentials > New. Label: MailOdds. URL: https://api.mailodds.com. Identity Type: Named Principal. Authentication Protocol: No Authentication (we pass the key in the header).
In the Named Credential, enable "Generate Authorization Header" = false. Instead, add a Custom Header: Authorization = Bearer mo_live_YOUR_KEY.
Setup > Remote Site Settings > New. Name: MailOdds_API. URL: https://api.mailodds.com. Active: checked.
On Lead/Contact: Email_Validation_Status__c (Picklist), Email_Validation_Action__c (Text), Email_Sub_Status__c (Text), Email_Validated_At__c (DateTime).
How Salesforce fields map to MailOdds API fields. Use this as a reference when creating custom fields and writing Apex code.
| Salesforce Field | MailOdds API Field | Direction |
|---|---|---|
Lead.Email / Contact.Email | email in POST /v1/validate | SF to MO |
Email_Validation_Status__c | result.status | MO to SF |
Email_Validation_Action__c | result.action | MO to SF |
Email_Sub_Status__c | result.sub_status | MO to SF |
Email_Validated_At__c | Set to Datetime.now() on response | SF internal |
Engagement_Score__c (Lead) | Computed from webhook events | MO to SF |
Last_Upsell_Message_Id__c (Opp) | result.message_id from POST /v1/deliver | MO to SF |
MailOdds_Config__c.Webhook_Secret | HMAC verification key | Shared secret |
Every response includes X-RateLimit-Limit and X-RateLimit-Remaining headers. On 429, honor the Retry-After header. Salesforce governor limits (100 callouts/transaction) will usually be your bottleneck before hitting API rate limits.
All webhook handlers in the patterns below reference this shared utility. Deploy it once and every @RestResource endpoint gets HMAC-SHA256 (a cryptographic signature that proves the webhook came from MailOdds and has not been tampered with) verification. Store your webhook secret in MailOdds_Config__c.Webhook_Secret__c (Protected Custom Setting).
// Reusable HMAC-SHA256 Webhook Verification Utility
// All webhook handlers call WebhookVerifier.verify(req)
public class WebhookVerifier {
public static Boolean verify(RestRequest req) {
String signature =
req.headers.get('X-MailOdds-Signature');
String secret = MailOdds_Config__c.getOrgDefaults()
.Webhook_Secret__c;
if (String.isBlank(signature)
|| String.isBlank(secret)) return false;
Blob expectedMac = Crypto.generateMac(
'HmacSHA256',
req.requestBody,
Blob.valueOf(secret)
);
String expected =
EncodingUtil.convertToHex(expectedMac);
return constantTimeEquals(signature, expected);
}
// Prevent timing attacks
private static Boolean constantTimeEquals(
String a, String b
) {
if (a.length() != b.length()) return false;
Integer diff = 0;
for (Integer i = 0; i < a.length(); i++) {
diff |= a.charAt(i) ^ b.charAt(i);
}
return diff == 0;
}
} 15 production-ready patterns, from lead intake gating to content classification and blacklist monitoring. Each includes working Apex code.
Validates emails on lead creation using @future(callout=true) (Apex annotation that runs HTTP calls in the background, outside the triggering transaction), auto-corrects typos via the suggested_email field, and quarantines rejects to prevent bad data from reaching your pipeline.
Apply different validation rules per business unit using Custom Metadata Type mappings and a policy_id (a reference to a validation rule set you create in MailOdds to customize pass/fail criteria). Enterprise divisions can enforce strict reject-on-risky while SMB can allow catch-all (a mail server that accepts messages for any address at the domain, even ones that do not exist) addresses.
Schedule a nightly bulk validation job via the API, then use Platform Events (Salesforce's real-time publish-subscribe messaging system for decoupled integrations) to write results back as they complete. Webhook-driven writeback eliminates polling.
Automatically sync hard bounces from Pardot/MCAE into the MailOdds suppression list, creating a shared blocklist across your email infrastructure.
Compute an aggregate email health score for each Account by batch-validating all Contact emails. Surface the score on the Account record for CSMs and AEs.
Run standard (free) validation on all inbound leads. Only spend credits on enhanced SMTP validation (standard checks syntax and DNS only; enhanced adds SMTP mailbox verification for higher accuracy) for leads that pass qualification. Typical savings: 72%.
Block competitor and freemail domains at the gate. Apply different filtering rules per lead source using validation policies with domain filter rules.
Pull telemetry data into native Salesforce dashboards. Track validation volume, hit rates, and credit consumption with ETag caching for efficiency.
Double-gate at Lead conversion: check the suppression list first, then re-validate the email. Prevents converting leads with known-bad addresses into Contacts and Opportunities.
Run a pre-flight validation audit on campaign member lists before sending. Generate a risk report with a Go/No-Go recommendation based on valid-email ratios.
Monitor your sending IPs and domains against DNS blacklists. A scheduled Apex job checks monitor status and auto-creates a Case when a listing is detected, with full blacklist details for remediation.
Run a spam score check before sending any transactional or campaign email from Salesforce. Checks domain reputation, link safety, and subject line quality. Blocks sends that score above a configurable threshold.
When an Email-to-Case arrives, run LLM-powered content classification to detect purchase intent, technical issues, or complaints. Auto-route Cases to the right queue and set priority based on content signals.
Run SMTP handshake tests and MX configuration audits for your sending domains. A scheduled job checks domain health weekly and creates Tasks when STARTTLS, certificate, or MX issues are detected.
Query the full delivery and engagement timeline for any message_id stored on a Salesforce record. Returns delivered/bounced status, human vs. bot opens, clicks with URLs, and unsubscribe status. Useful for debugging delivery issues without parsing webhook logs.
10 patterns that turn email delivery signals into pipeline actions. Webhook-driven engagement scoring, bounce forensics, deferral monitoring, and transactional sending with structured data.
is_bot = true when the event came from a security scanner, link prefetcher, or corporate email gateway (not a human). Common with Barracuda, Proofpoint, and Mimecast.
is_mpp = true when the event came from Apple Mail Privacy Protection, which pre-fetches all images and inflates open counts. Affects roughly 50% of iOS/macOS mail users.
Both fields are Booleans on engagement events (opened, clicked). Always guard with == true since they may be absent on non-engagement events.
Receive webhook click events, verify HMAC-SHA256 (a cryptographic signature that proves the webhook came from MailOdds and has not been tampered with) signatures, filter bot and Apple MPP noise, then bump engagement scores on Leads. Creates a high-priority Task when the score crosses a configurable threshold.
Poll the suppression audit log on a schedule, find hard-bounced contacts, cross-reference Accounts for alternate contacts, and create win-back Tasks for the sales team.
Fire a transactional upsell email with JSON-LD structured data when an Opportunity closes. Uses campaign_type, schema_data, and ai_summary for rich inbox rendering. Stores the returned message_id for engagement tracking.
Poll your sending domain identity score on a schedule. When the grade drops below a configurable threshold, auto-switch to a fallback domain and alert the team with a full breakdown.
Block Opportunity Close-Won on high-value deals when the primary contact email is unvalidated or rejected. Creates an smtp_required policy via preset, validates with policy_id, and creates an urgent Task on rejection.
Receive bounce webhooks with full SMTP forensics: bounce_type, smtp_code, enhanced_status_code, smtp_response, mx_host, and isp. Logs to a custom object and auto-suppresses hard bounces.
Track delivered and human-opened events via webhook (filtering is_bot and is_mpp). A scheduled job finds "zombie" contacts with sends but zero human opens, then re-validates to detect stale addresses.
When a Campaign becomes Active, create a MailOdds subscriber list and sync all members. Uses Queueable (Salesforce async job pattern that allows chaining multiple jobs with separate governor limit budgets) chaining to handle lists larger than the 100-callout governor limit.
Companion to the Transactional Upsell pattern. Matches incoming webhook open/click events by message_id to the Opportunity record. Filters bots and creates a high-priority Task for the AE on first human open.
Receive deferral webhooks with smtp_code, smtp_response, mx_host, isp, and attempts. Logs to a custom object and fires an alert when any ISP exceeds a configurable hourly deferral threshold.
MailOdds is a full-cycle email platform. Use these Apex patterns to send transactional and campaign email, monitor sender health and DMARC compliance, run spam checks, classify content, and track engagement directly from Salesforce.
Trigger transactional emails from Salesforce events (new opportunity, case update, approval) through the MailOdds delivery API. Uses Named Credentials, domain_id for DKIM signing, and validate_first option for pre-send email checks. Returns message_id for engagement tracking.
// Apex: Send Transactional Email via Named Credential
public class MailOddsSendService {
@future(callout=true)
public static void sendEmail(
String toEmail, String subject,
String htmlBody, String domainId
) {
try {
HttpRequest req = new HttpRequest();
req.setEndpoint(
'callout:MailOdds/v1/deliver');
req.setMethod('POST');
req.setHeader('Content-Type',
'application/json');
req.setTimeout(120000);
Map<String, Object> body =
new Map<String, Object>{
'to' => new List<Map<String, String>>{
new Map<String, String>{
'email' => toEmail
}
},
'from' => 'notifications@'
+ getDomainName(domainId),
'domain_id' => domainId,
'subject' => subject,
'html' => htmlBody,
'tags' => new List<String>{
'salesforce-triggered'
},
'track_opens' => true,
'track_clicks' => true,
'options' => new Map<String, Object>{
'validate_first' => true
}
};
req.setBody(JSON.serialize(body));
HttpResponse res = new Http().send(req);
if (res.getStatusCode() == 200) {
Map<String, Object> result =
(Map<String, Object>)
JSON.deserializeUntyped(
res.getBody());
// Store result.message_id for
// engagement tracking via
// GET /v1/message-events
}
} catch (Exception ex) {
System.debug(LoggingLevel.ERROR,
'SendEmail error: ' + ex.getMessage());
}
}
private static String getDomainName(String id) {
Sending_Domain_Config__mdt cfg = [
SELECT Domain_Name__c
FROM Sending_Domain_Config__mdt
WHERE Domain_Id__c = :id LIMIT 1
];
return cfg?.Domain_Name__c ?? 'mail.example.com';
}
} Related: Email Sending API
Send a single message to up to 100 Campaign members in one API call. Shares the same message body across all recipients, each processed independently. For campaigns over 100 members, chain Queueable jobs.
// Apex: Batch Deliver to Campaign Members (max 100)
public class BatchDeliverService {
@future(callout=true)
public static void sendToCampaignMembers(
Id campaignId, String domainId,
String subject, String htmlBody
) {
try {
List<CampaignMember> members = [
SELECT Contact.Email, Contact.Name,
Lead.Email, Lead.Name
FROM CampaignMember
WHERE CampaignId = :campaignId
LIMIT 100
];
List<Map<String, String>> recipients =
new List<Map<String, String>>();
for (CampaignMember cm : members) {
String email = cm.Contact?.Email != null
? cm.Contact.Email : cm.Lead?.Email;
String name = cm.Contact?.Name != null
? cm.Contact.Name : cm.Lead?.Name;
if (email == null) continue;
recipients.add(new Map<String, String>{
'email' => email,
'name' => name
});
}
HttpRequest req = new HttpRequest();
req.setEndpoint(
'callout:MailOdds/v1/deliver/batch');
req.setMethod('POST');
req.setHeader('Content-Type',
'application/json');
req.setTimeout(120000);
req.setBody(JSON.serialize(
new Map<String, Object>{
'to' => recipients,
'from' => 'campaigns@'
+ getDomainName(domainId),
'domain_id' => domainId,
'subject' => subject,
'html' => htmlBody,
'track_opens' => true,
'track_clicks' => true
}
));
HttpResponse res = new Http().send(req);
// 202 Accepted: batch queued for delivery
} catch (Exception ex) {
System.debug(LoggingLevel.ERROR,
'BatchDeliver error: ' + ex.getMessage());
}
}
private static String getDomainName(String id) {
Sending_Domain_Config__mdt cfg = [
SELECT Domain_Name__c
FROM Sending_Domain_Config__mdt
WHERE Domain_Id__c = :id LIMIT 1
];
return cfg?.Domain_Name__c ?? 'mail.example.com';
}
} Create MailOdds campaigns from Salesforce, add A/B test variants with different subjects and weights, and trigger sending. Use the analytics endpoints (ab-results, funnel, delivery-confidence) to measure performance.
// Apex: Create and Send MailOdds Campaign from SF
public class CampaignSendService {
@future(callout=true)
public static void createAndSendCampaign(
Id sfCampaignId, String listId,
String domainId
) {
try {
Campaign camp = [
SELECT Name, Description
FROM Campaign WHERE Id = :sfCampaignId
LIMIT 1
];
// 1. Create campaign
HttpRequest createReq = new HttpRequest();
createReq.setEndpoint(
'callout:MailOdds/v1/campaigns');
createReq.setMethod('POST');
createReq.setHeader('Content-Type',
'application/json');
createReq.setTimeout(15000);
createReq.setBody(JSON.serialize(
new Map<String, Object>{
'name' => camp.Name,
'list_id' => listId,
'domain_id' => domainId
}
));
HttpResponse createRes =
new Http().send(createReq);
if (createRes.getStatusCode() != 201) return;
Map<String, Object> result =
(Map<String, Object>)
JSON.deserializeUntyped(
createRes.getBody());
Map<String, Object> campaign =
(Map<String, Object>)
result.get('campaign');
String campaignId =
(String) campaign.get('id');
// 2. Add A/B variant
HttpRequest varReq = new HttpRequest();
varReq.setEndpoint(
'callout:MailOdds/v1/campaigns/'
+ campaignId + '/variants');
varReq.setMethod('POST');
varReq.setHeader('Content-Type',
'application/json');
varReq.setTimeout(10000);
varReq.setBody(JSON.serialize(
new Map<String, Object>{
'subject' => camp.Name,
'html' => '<h1>' + camp.Name + '</h1>'
+ '<p>' + camp.Description + '</p>',
'weight' => 100
}
));
new Http().send(varReq);
// 3. Send campaign
HttpRequest sendReq = new HttpRequest();
sendReq.setEndpoint(
'callout:MailOdds/v1/campaigns/'
+ campaignId + '/send');
sendReq.setMethod('POST');
sendReq.setTimeout(15000);
new Http().send(sendReq);
} catch (Exception ex) {
System.debug(LoggingLevel.ERROR,
'CampaignSend error: '
+ ex.getMessage());
}
}
} Three @AuraEnabled (Apex annotation that exposes a method to Lightning Web Components for UI display) methods for a Lightning Web Component dashboard: current sender health (score, grade, bounce/complaint rates), historical trend (daily data points), and sending statistics (delivery/open/click rates by period). All use Named Credentials.
// Apex: Fetch Sender Health + Trend for LWC Dashboard
public with sharing class SenderHealthController {
@AuraEnabled(cacheable=true)
public static Map<String, Object> getSenderHealth() {
HttpRequest req = new HttpRequest();
req.setEndpoint(
'callout:MailOdds/v1/sender-health');
req.setMethod('GET');
req.setTimeout(10000);
HttpResponse res = new Http().send(req);
if (res.getStatusCode() != 200) {
throw new AuraHandledException(
'API returned ' + res.getStatusCode());
}
return (Map<String, Object>)
JSON.deserializeUntyped(res.getBody());
}
@AuraEnabled(cacheable=true)
public static Map<String, Object>
getSenderHealthTrend(String period) {
HttpRequest req = new HttpRequest();
req.setEndpoint(
'callout:MailOdds/v1/sender-health/trend'
+ '?period=' + (period ?? '30d'));
req.setMethod('GET');
req.setTimeout(10000);
HttpResponse res = new Http().send(req);
if (res.getStatusCode() != 200) {
throw new AuraHandledException(
'API returned ' + res.getStatusCode());
}
// Returns: period, data_points array with
// daily score, grade, delivery_rate, bounce_rate
return (Map<String, Object>)
JSON.deserializeUntyped(res.getBody());
}
@AuraEnabled(cacheable=true)
public static Map<String, Object>
getSendingStats(String period) {
HttpRequest req = new HttpRequest();
req.setEndpoint(
'callout:MailOdds/v1/sending-stats'
+ '?period=' + (period ?? '7d'));
req.setMethod('GET');
req.setTimeout(10000);
HttpResponse res = new Http().send(req);
if (res.getStatusCode() != 200) {
throw new AuraHandledException(
'API returned ' + res.getStatusCode());
}
// Returns: stats.sent, delivered, bounced,
// open_rate, click_rate, complaint_rate
return (Map<String, Object>)
JSON.deserializeUntyped(res.getBody());
}
} Related: Sender Reputation
Register domains, verify DNS records, check trends (daily pass/fail), get sending source analysis (which IPs send for your domain with DKIM/SPF/DMARC alignment, email authentication DNS standards that prove messages came from authorized servers), and receive policy upgrade recommendations. All via scheduled Apex.
// Apex: Full DMARC Suite from Salesforce
public class MailOddsDmarcService {
// Register a domain for DMARC monitoring
@future(callout=true)
public static void registerDomain(String domain) {
try {
HttpRequest req = new HttpRequest();
req.setEndpoint(
'callout:MailOdds/v1/dmarc-domains');
req.setMethod('POST');
req.setHeader('Content-Type',
'application/json');
req.setTimeout(10000);
req.setBody(JSON.serialize(
new Map<String, String>{
'domain' => domain
}
));
HttpResponse res = new Http().send(req);
if (res.getStatusCode() == 201) {
Map<String, Object> data =
(Map<String, Object>)
JSON.deserializeUntyped(
res.getBody());
// Store domain_id for verify/sources/trend
}
} catch (Exception ex) {
System.debug(LoggingLevel.ERROR,
'DMARC register error: '
+ ex.getMessage());
}
}
// Verify DMARC DNS records are configured
@future(callout=true)
public static void verifyDomain(String domainId) {
try {
HttpRequest req = new HttpRequest();
req.setEndpoint(
'callout:MailOdds/v1/dmarc-domains/'
+ domainId + '/verify');
req.setMethod('POST');
req.setTimeout(10000);
new Http().send(req);
} catch (Exception ex) {
System.debug(LoggingLevel.ERROR,
'DMARC verify error: '
+ ex.getMessage());
}
}
// Scheduled: check DMARC trend and alert on failures
@future(callout=true)
@TestVisible
public static void checkDmarcHealth(
String domainId
) {
try {
// 1. Get trend data
HttpRequest trendReq = new HttpRequest();
trendReq.setEndpoint(
'callout:MailOdds/v1/dmarc-domains/'
+ domainId + '/trend?days=7');
trendReq.setMethod('GET');
trendReq.setTimeout(10000);
HttpResponse trendRes =
new Http().send(trendReq);
// 2. Get policy recommendation
HttpRequest recReq = new HttpRequest();
recReq.setEndpoint(
'callout:MailOdds/v1/dmarc-domains/'
+ domainId + '/recommendation');
recReq.setMethod('GET');
recReq.setTimeout(10000);
HttpResponse recRes =
new Http().send(recReq);
// 3. Get sending sources
HttpRequest srcReq = new HttpRequest();
srcReq.setEndpoint(
'callout:MailOdds/v1/dmarc-domains/'
+ domainId + '/sources?days=7');
srcReq.setMethod('GET');
srcReq.setTimeout(10000);
HttpResponse srcRes =
new Http().send(srcReq);
// Aggregate findings into Task
if (trendRes.getStatusCode() == 200) {
Map<String, Object> trend =
(Map<String, Object>)
JSON.deserializeUntyped(
trendRes.getBody());
List<Object> points =
(List<Object>) trend.get(
'data_points');
// Check last 7 days for failures
if (points != null
&& !points.isEmpty()) {
Map<String, Object> latest =
(Map<String, Object>)
points[points.size() - 1];
Integer failCount =
(Integer) latest.get(
'fail_count');
if (failCount > 0) {
insert new Task(
Subject = 'DMARC failures '
+ 'detected ('
+ failCount + ' in 7d)',
Description =
'Trend: '
+ trendRes.getBody()
.left(2000)
+ '\nSources: '
+ srcRes.getBody()
.left(2000)
+ '\nRecommendation: '
+ recRes.getBody()
.left(1000),
Priority = 'High',
ActivityDate = Date.today()
);
}
}
}
} catch (Exception ex) {
System.debug(LoggingLevel.ERROR,
'DMARC health check error: '
+ ex.getMessage());
}
}
} Related: DMARC Monitoring
Fetch bounce analyses, drill into per-record bounce details (email, bounce_type, smtp_code), and update matching Salesforce Contacts with bounce status. Cross-references the full analysis chain.
// Apex: Cross-Reference Bounces with Contacts
public class MailOddsBounceSync {
@future(callout=true)
public static void syncBounces() {
try {
HttpRequest req = new HttpRequest();
req.setEndpoint(
'callout:MailOdds/v1/bounce-analyses');
req.setMethod('GET');
req.setTimeout(15000);
HttpResponse res = new Http().send(req);
if (res.getStatusCode() != 200) return;
Map<String, Object> data =
(Map<String, Object>)
JSON.deserializeUntyped(res.getBody());
List<Object> analyses =
(List<Object>) data.get('analyses');
Set<String> bouncedEmails =
new Set<String>();
for (Object a : analyses) {
Map<String, Object> analysis =
(Map<String, Object>) a;
// Fetch bounce records for each analysis
String analysisId =
(String) analysis.get('id');
HttpRequest recReq = new HttpRequest();
recReq.setEndpoint(
'callout:MailOdds/v1/bounce-analyses/'
+ analysisId + '/records');
recReq.setMethod('GET');
recReq.setTimeout(10000);
HttpResponse recRes =
new Http().send(recReq);
if (recRes.getStatusCode() != 200)
continue;
Map<String, Object> recData =
(Map<String, Object>)
JSON.deserializeUntyped(
recRes.getBody());
List<Object> records =
(List<Object>) recData.get(
'records');
for (Object r : records) {
Map<String, Object> rec =
(Map<String, Object>) r;
bouncedEmails.add(
(String) rec.get('email'));
}
}
if (bouncedEmails.isEmpty()) return;
List<Contact> contacts = [
SELECT Id, Email FROM Contact
WHERE Email IN :bouncedEmails
];
for (Contact c : contacts) {
c.MailOdds_Bounce_Status__c = 'Bounced';
c.MailOdds_Bounce_Synced_At__c =
Datetime.now();
}
update contacts;
} catch (Exception ex) {
System.debug(LoggingLevel.ERROR,
'BounceSync error: ' + ex.getMessage());
}
}
} Related: Email Deliverability Platform
Fetch suppression list statistics for an LWC dashboard widget. Shows total entries, breakdown by type, and recent additions. Complements the Suppression Audit pattern in Revenue Intelligence.
// Apex: Fetch Suppression Stats for Dashboard
public with sharing class SuppressionStatsController {
@AuraEnabled(cacheable=true)
public static Map<String, Object>
getSuppressionStats() {
HttpRequest req = new HttpRequest();
req.setEndpoint(
'callout:MailOdds/v1/suppression/stats');
req.setMethod('GET');
req.setTimeout(10000);
HttpResponse res = new Http().send(req);
if (res.getStatusCode() != 200) {
throw new AuraHandledException(
'API returned ' + res.getStatusCode());
}
// Returns: total_entries, by_type breakdown,
// recent additions
return (Map<String, Object>)
JSON.deserializeUntyped(res.getBody());
}
} Find contacts with no engagement (opens, clicks) in a configurable period. Flag matching Salesforce Contacts and create re-engagement Tasks for the sales team. Run as a scheduled job for automated list hygiene.
// Apex: Identify Inactive Contacts for Re-Engagement
public class InactiveContactReportService {
@future(callout=true)
public static void findInactiveContacts(
Integer inactiveDays
) {
try {
HttpRequest req = new HttpRequest();
req.setEndpoint(
'callout:MailOdds/v1/contacts'
+ '/inactive-report?days='
+ inactiveDays);
req.setMethod('GET');
req.setTimeout(15000);
HttpResponse res = new Http().send(req);
if (res.getStatusCode() != 200) return;
Map<String, Object> data =
(Map<String, Object>)
JSON.deserializeUntyped(res.getBody());
List<Object> inactive =
(List<Object>) data.get('contacts');
if (inactive == null
|| inactive.isEmpty()) return;
Set<String> emails = new Set<String>();
for (Object c : inactive) {
Map<String, Object> contact =
(Map<String, Object>) c;
emails.add(
(String) contact.get('email'));
}
// Flag matching Salesforce Contacts
List<Contact> sfContacts = [
SELECT Id, Email FROM Contact
WHERE Email IN :emails
];
List<Task> tasks = new List<Task>();
for (Contact c : sfContacts) {
c.Engagement_Status__c = 'Inactive';
tasks.add(new Task(
WhoId = c.Id,
Subject = 'Re-engage: No activity '
+ 'in ' + inactiveDays + ' days',
Priority = 'Normal',
ActivityDate = Date.today().addDays(7)
));
}
update sfContacts;
if (!tasks.isEmpty()) insert tasks;
} catch (Exception ex) {
System.debug(LoggingLevel.ERROR,
'InactiveReport error: '
+ ex.getMessage());
}
}
} Can't find what you're looking for? We're here to help you get Salesforce working.
Get 1,000 free validations. Start with one pattern, scale to the full platform.