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)


Leave a comment: