SMS Providers
Collapse
This is a sticky topic.
X
X
-
I created an extension, which uses the SMS Providers Extension to let users send SMS from Contacts, etc.
Forum post is here.Hey Everyone, I needed a way to let staff send ad-hoc SMS's when looking at contacts and some custom Entities. I've created that as an extension and put it on Github if anyone else wants to use it or use it as a base for their own tweaks. We use this with branded SMS's, so I didn't need to handle replies. Code is here -Comment
-
SMSPlanet integration for EspoCRM (custom provider + GSM normalization)
SMS Plante can send worldwide. I use it for Poland.
I’ve implemented a working SMSPlanet provider for EspoCRM SMS extension and wanted to share it with the community. yuri please add to the repo if it makes sense for you
This solution:- integrates with SMSPlanet API
- supports Bearer token or key/password auth
- forces GSM-7 encoding (removes Polish diacritics & smart characters → avoids 70-char SMS limit)
- sets fixed sender: SSkladki.pl
- works with Formula (ext\sms\send) and automations
- SMSPlanet API (https://api2.smsplanet.pl/sms)
- GSM normalization (ą→a, ę→e, etc.)
- Handles Word/Outlook characters (quotes, dashes, etc.)
- Multipart detection (logs segments)
- Works with EspoCRM Sms entity
- Production tested
Create:
custom/Espo/Modules/SmsProviders/SmsPlanet/SmsPlanetSender.php Full code:
<?php
namespace Espo\Modules\SmsProviders\SmsPlanet;
use Espo\Core\Exceptions\Error;
use Espo\Core\Sms\Sender;
use Espo\Core\Sms\Sms;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Log;
use Espo\Core\Utils\Metadata;
use Espo\Entities\Integration;
use Espo\ORM\EntityManager;
class SmsPlanetSender implements Sender
{
private const BASE_URL = 'https://api2.smsplanet.pl';
private const TIMEOUT = 30;
private const FROM_NAME = 'SSkladki.pl';
private const FORCE_ASCII = true;
private Config $config;
private EntityManager $entityManager;
private Log $log;
private Metadata $metadata;
public function __construct(
Config $config,
EntityManager $entityManager,
Log $log,
Metadata $metadata
) {
$this->config = $config;
$this->entityManager = $entityManager;
$this->log = $log;
$this->metadata = $metadata;
}
public function send(Sms $sms): void
{
foreach ($sms->getToNumberList() as $number) {
$this->sendToNumber($sms, $number);
}
}
private function sendToNumber(Sms $sms, string $toNumber): void
{
$integration = $this->entityManager
->getEntity(Integration::ENTITY_TYPE, 'SmsPlanet');
if (!$integration || !$integration->get('enabled')) {
throw new Error('SmsPlanet integration not enabled.');
}
$token = $integration->get('smsPlanetToken');
if (!$token) {
throw new Error('SmsPlanet: API token required.');
}
$body = $sms->getBody();
if (self::FORCE_ASCII) {
$body = self::normalizeToGsm7($body);
}
$to = self::formatNumber($toNumber);
$params = http_build_query([
'from' => self::FROM_NAME,
'to' => $to,
'msg' => $body,
]);
$ch = curl_init(self::BASE_URL . '/sms');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $params,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/x-www-form-urlencoded',
]
]);
$response = curl_exec($ch);
$error = curl_errno($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($error) {
throw new Error('SmsPlanet cURL error');
}
if ($status !== 200) {
throw new Error('SmsPlanet HTTP error: ' . $status);
}
$decoded = json_decode($response, true);
if (empty($decoded['messageId'])) {
throw new Error('SmsPlanet: Invalid response');
}
}
private static function formatNumber(string $number): string
{
$digits = preg_replace('/[^0-9]/', '', $number);
if (strlen($digits) === 9) {
return '+48' . $digits;
}
if (strlen($digits) === 11 && substr($digits, 0, 2) === '48') {
return '+' . $digits;
}
return '+' . $digits;
}
private static function normalizeToGsm7(string $text): string
{
$map = [
'ą'=>'a','ć'=>'c','ę'=>'e','ł'=>'l','ń'=>'n','ó'=> 'o','ś'=>'s','ż'=>'z','ź'=>'z',
'Ą'=>'A','Ć'=>'C','Ę'=>'E','Ł'=>'L','Ń'=>'N','Ó'=> 'O','Ś'=>'S','Ż'=>'Z','Ź'=>'Z'
];
$text = strtr($text, $map);
$text = strtr($text, [
'–'=>'-','—'=>'-','„'=>'"','”'=>'"',
'’'=>"'",'…'=>'...','€'=>'EUR'
]);
return preg_replace('/[^\x20-\x7E]/u', '', $text);
}
}
Usage (Formula example)
$body = 'Test SMS from EspoCRM';
$phoneNumber = phoneNumber;
// normalize PL numbers
$phoneNumber = string\replace($phoneNumber, ' ', '');
ifThen(
string\length($phoneNumber) == 9,
$phoneNumber = string\concatenate('+48', $phoneNumber)
);
$smsId = record\create(
'Sms',
'to', $phoneNumber,
'body', $body
);
ext\sms\send($smsId);
Notes- Polish characters → GSM conversion prevents SMS splitting (160 vs 70 chars)
- Numbers without prefix are auto-converted to +48
- Works with workflows, formulas, and automation
Next improvements (if anyone interested)- SMS Templates (like Email Templates)
- UI modal for sending SMS from Lead/Contact
- Delivery status (DLR webhook)
Comment

Comment