Real Messaging Engine

Apex APIs

The Apex surface is a global, versioned API in the rme namespace.

Builder API

RME_Message.Result result = RME_Message.builder()
    .channel('EMAIL')
    .templateName('Order_Confirmation')
    .templateLanguage('en_US')
    .toContact(contactId)
    .recordId(orderId)
    .category('Transactional')
    .fromDomain('example.com')
    .replyTo('support@example.com')
    .parameter('promoCode', 'WELCOME10')
    .attachment(contentVersionId)
    .schedule('LOCAL', '09:00')
    .priority('HIGH')
    .send();

Result

public class Result {
    public Id messageId;          // Message__c record id
    public String status;         // SENT | QUEUED | SCHEDULED | SUPPRESSED | FAILED
    public String resolvedChannel;
    public String provider;
    public String providerMessageId;
    public String errorCode;
    public String errorMessage;
}

Bulk send

List<RME_OutboundMessage> messages = new List<RME_OutboundMessage>();
for (Contact c : [SELECT Id FROM Contact WHERE Renewal_Due__c = TRUE LIMIT 5000]) {
    messages.add(RME_Message.builder()
        .channel('EMAIL')
        .templateName('Renewal_Notice')
        .toContact(c.Id)
        .build());
}
List<RME_Message.Result> results = RME_Message.sendAll(messages);   // synchronous, chunked
// or
System.enqueueJob(new RME_BulkSendQueueable(messages, 200));        // asynchronous

Invocable Apex

global with sharing class RME_SendMessageAction {
    global class Request {
        @InvocableVariable(required=true) global String channel;
        @InvocableVariable global String templateName;
        @InvocableVariable global Id contactId;
        @InvocableVariable global Id contextRecordId;
        @InvocableVariable global String category;
    }
    global class Response {
        @InvocableVariable global Id messageId;
        @InvocableVariable global String status;
        @InvocableVariable global String errorMessage;
    }

    @InvocableMethod(label='Send Message (RME)' category='Real Messaging Engine')
    global static List<Response> send(List<Request> requests) { /* ... */ return null; }
}

Services

ClassPurpose
RME_MessageBuild and send
RME_SchedulerSchedule, cancel, reschedule
RME_ConsentRead and write consent per channel and category
RME_SuppressionAdd, remove, and test suppression
RME_TemplateRendererRender and preview templates
RME_DeliveryStatusQuery normalized delivery state
RME_DiagnosticsConfiguration and health checks

Governor limits

RME issues one callout per provider request. Callouts cannot run in the same transaction as prior DML in a trigger context — use sendAsync() or a Queueable:

trigger OrderTrigger on Order (after update) {
    System.enqueueJob(new OrderConfirmationQueueable(Trigger.newMap.keySet()));
}
LimitGuidance
100 callouts / transactionChunk bulk sends; default chunk 200 messages per Queueable
10 s callout timeoutRME sets 20 s where allowed and retries on timeout
50 Queueable chainsRME_BulkSendQueueable self-chains within the limit
No callout after DMLUse async entry points from triggers

Testing

@IsTest
private class OrderConfirmationTest {
    @IsTest static void sendsConfirmation() {
        RME_Test.mockProvider('SENDGRID', 202, '{"id":"test-123"}');
        Test.startTest();
        RME_Message.Result r = RME_Message.builder()
            .channel('EMAIL').toAddress('qa@example.com')
            .subject('Test').body('Body').send();
        Test.stopTest();
        System.assertEquals('SENT', r.status);
        System.assertEquals(1, [SELECT COUNT() FROM rme__Message__c]);
    }
}

RME_Test.mockProvider installs an HttpCalloutMock for the named provider so tests never require real credentials.

Continue to Platform Events.

Was this helpful?

Last updated 1 month ago