SMS and MMS
This document describes how to extend Mautic’s SMS capabilities by building a custom transport in a Plugin. It walks through implementing the transport interfaces, adding bulk and MMS support, registering the transport, and hooking into the Contact filtering pipeline.
Note
Extending generally works by hooking into events using event listeners or subscribers. Read more about them in the Event listeners section.
Transport interfaces
A custom transport implements one or more interfaces from the Mautic\SmsBundle\Sms namespace, depending on the capabilities it provides. These interfaces live in Mautic core, so your Plugin implements them rather than redefining them.
TransportInterface - the base interface every transport must implement. It defines
sendSms(Lead $lead, $content), which sends a single message and returnstrueon success or an error message string on failure.BulkTransportInterface - extends
TransportInterfaceto enable native batch sending throughsendBatchSms(RecipientCollection $collection, string $content): RecipientCollection. Transports that implement onlyTransportInterfacefall back to iterative per-Contact sending.MMSTransportInterface - adds MMS support with media attachments through
sendMms(Lead $lead, string $content, array $media): bool|string. Because of carrier restrictions, MMS currently works only for recipients in the US, Canada, and Australia.
For the authoritative method signatures and documentation blocks, see the linked source files.
Building a custom SMS transport
For the general Plugin layout, see the Plugin structure section.
The worked example below builds a transport inside a Plugin named HelloWorldBundle. The transport-related files sit under the bundle’s Sms/Transport directory:
plugins/HelloWorldBundle/
├── Config/
│ └── config.php
└── Sms/
└── Transport/
└── HelloWorldTransport.php
Implementing the transport
Implement the interfaces for the capabilities your provider supports. The example below implements all three, so the transport handles single SMS, bulk SMS, and MMS.
<?php
// plugins/HelloWorldBundle/Sms/Transport/HelloWorldTransport.php
declare(strict_types=1);
namespace MauticPlugin\HelloWorldBundle\Sms\Transport;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\SmsBundle\Collection\RecipientCollection;
use Mautic\SmsBundle\Sms\BulkTransportInterface;
use Mautic\SmsBundle\Sms\MMSTransportInterface;
use Mautic\SmsBundle\Sms\TransportInterface;
class HelloWorldTransport implements TransportInterface, BulkTransportInterface, MMSTransportInterface
{
public function sendSms(Lead $lead, $content)
{
$phone = $lead->getPhone();
if (empty($phone)) {
return 'No phone number available';
}
// Send the SMS through your provider here.
// Return true on success or an error message string on failure.
return true;
}
public function sendBatchSms(RecipientCollection $collection, string $content): RecipientCollection
{
foreach ($collection as $recipient) {
$lead = $recipient->getLead();
// getFinalMessage() returns the message with tokens already
// replaced for this recipient.
$message = $recipient->getFinalMessage();
$success = $this->sendToProvider($lead, $message);
// Record the outcome so Mautic can report per-Contact results.
$recipient->setResult($success);
}
return $collection;
}
public function sendMms(Lead $lead, string $content, array $media): bool|string
{
// Send the MMS with its media attachments here.
return true;
}
}
Registering the transport
Register the transport in your Plugin’s Config/config.php by tagging the service with mautic.sms_transport. Mautic builds Plugin config.php services through its own ServicePass compiler pass rather than Symfony autoconfiguration, so implementing TransportInterface doesn’t tag the service for you, and you must declare the tag explicitly. The SmsTransportPass compiler pass then collects every service carrying this tag, and the integrationAlias tag argument sets the name shown in the UI.
<?php
// plugins/HelloWorldBundle/Config/config.php
return [
'services' => [
'other' => [
'mautic.sms.transport.helloworld' => [
'class' => \MauticPlugin\HelloWorldBundle\Sms\Transport\HelloWorldTransport::class,
'tag' => 'mautic.sms_transport',
'tagArguments' => [
'integrationAlias' => 'Hello World SMS',
],
],
],
],
];
To handle delivery callbacks from your provider, register a callback handler service with the mautic.sms_callback_handler tag. Mautic’s built-in TwilioTransport is a useful reference for a complete transport and callback implementation.
Bulk sending and recipient data
When a transport implements BulkTransportInterface, Mautic passes a RecipientCollection to sendBatchSms(). The collection extends \ArrayIterator, so you can iterate it directly, and each item is an SmsRecipientDTO.
Each Data Transfer Object - DTO - wraps one Contact together with its token data. The methods most relevant to a transport are:
getLead()- Returns theLeadentity for this recipient.getFinalMessage()- Returns the message body with tokens already replaced for this recipient.getSubstitutionData()- Returns the raw token values, useful for providers that perform their own substitution.setResult(bool $result)- Records whether the send succeeded so Mautic can report per-Contact outcomes.
Contact filtering events
Three events fire sequentially during SMS sending to filter Contacts before dispatch. Subscribe to them to exclude Contacts based on custom criteria.
For how to register a subscriber, see the listeners and subscribers section.
Do Not Contact filter
Use SmsEvents::DNC_FILTER_CONTACTS_ON_SEND to filter Contacts based on Do Not Contact status.
<?php
declare(strict_types=1);
use Mautic\SmsBundle\Event\DncEvent;
use Mautic\SmsBundle\SmsEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
final class SmsFilterSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
SmsEvents::DNC_FILTER_CONTACTS_ON_SEND => ['onDncFilter', 0],
];
}
public function onDncFilter(DncEvent $event): void
{
foreach ($event->getContacts() as $id => $contact) {
if ($this->shouldExclude($contact)) {
$event->removeContact($id);
}
}
}
}
Queue filter
Use SmsEvents::QUEUE_FILTER_CONTACTS_ON_SEND to filter Contacts based on frequency rules or queueing logic. Subscribe to it the same way, with a listener that receives a QueueEvent.
Generic filter
Use SmsEvents::FILTER_CONTACTS_ON_SEND for any remaining filtering logic, such as removing Contacts without phone numbers. Its listener receives a FilterEvent.
All three event classes share a common API, shown here for FilterEvent:
getContacts()- Returns the array of ContactsremoveContact(int $id)- Remove a single Contact by IDremoveContacts(array $contacts)- Remove multiple ContactsgetRemovedContacts()- Get the list of removed Contacts
Campaign SMS events
When integrating SMS with Campaigns, use the batch Campaign action event for better performance.
Batch action event
Use SmsEvents::ON_CAMPAIGN_TRIGGER_BATCH_ACTION to handle Campaign SMS actions. Set it as the batchEventName when registering the action on CampaignEvents::CAMPAIGN_ON_BUILD. See Campaigns for the full Campaign action workflow.
<?php
use Mautic\CampaignBundle\Event\CampaignBuilderEvent;
use Mautic\SmsBundle\SmsEvents;
public function onCampaignBuild(CampaignBuilderEvent $event): void
{
$event->addAction(
'my_plugin.send_sms',
[
'label' => 'Send Custom SMS',
'batchEventName' => SmsEvents::ON_CAMPAIGN_TRIGGER_BATCH_ACTION,
// Other configuration...
]
);
}
SMS event constants
The SmsEvents class defines all SMS-related event constants:
Event constant |
Description |
|---|---|
|
Fires when a Campaign triggers an SMS action for a batch of Contacts. |
|
Fires when a Campaign triggers an SMS action for a single Contact. |
|
Fires to filter Contacts based on Do Not Contact status. |
|
Fires to filter Contacts based on frequency rules. |
|
Fires for generic Contact filtering before SMS dispatch. |