Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions src/Utopia/Messaging/Adapter/SMS/VonageMessages.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

namespace Utopia\Messaging\Adapter\SMS;

use Utopia\Messaging\Adapter\SMS as SMSAdapter;
use Utopia\Messaging\Adapter\VonageMessagesBase;
use Utopia\Messaging\Messages\SMS as SMSMessage;
use Utopia\Messaging\Response;

/**
* Vonage Messages API SMS Adapter.
*
* Uses the newer Vonage Messages API (V1) instead of the older SMS API.
* The Messages API is cheaper and supports multiple message types.
*
* Reference: https://developer.vonage.com/en/api/messages
*/
class VonageMessages extends SMSAdapter
{
use VonageMessagesBase;

protected const NAME = 'Vonage Messages';

/**
* @param string $apiKey Vonage API Key
* @param string $apiSecret Vonage API Secret
*/
public function __construct(
private string $apiKey,
private string $apiSecret,
private ?string $from = null
) {
}

/**
* Get adapter name.
*/
public function getName(): string
{
return static::NAME;
}

/**
* Get max messages per request.
*/
public function getMaxMessagesPerRequest(): int
{
return 1;
}

/**
* {@inheritdoc}
*/
protected function process(SMSMessage $message): array
{
$to = \ltrim($message->getTo()[0], '+');
$from = $this->from ?? $message->getFrom();
$from = $from !== null ? \ltrim($from, '+') : null;

$response = new Response($this->getType());

if (empty($from)) {
$response->addResult($message->getTo()[0], 'The "from" field is required for the Vonage Messages API.');
return $response->toArray();
}

$result = $this->request(
method: 'POST',
url: $this->getApiEndpoint(),
headers: $this->getRequestHeaders(),
body: [
'message_type' => 'text',
'to' => $to,
'from' => $from,
'text' => $message->getContent(),
'channel' => 'sms',
],
);

if ($result['statusCode'] === 202) {
$response->setDeliveredTo(1);
$response->addResult($message->getTo()[0]);
} else {
$errorMessage = 'Unknown error';
if (isset($result['response']['detail'])) {
$errorMessage = $result['response']['detail'];
} elseif (isset($result['response']['title'])) {
$errorMessage = $result['response']['title'];
} elseif (!empty($result['error'])) {
$errorMessage = $result['error'];
}

$response->addResult($message->getTo()[0], $errorMessage);
}

return $response->toArray();
}
}
50 changes: 50 additions & 0 deletions src/Utopia/Messaging/Adapter/VonageMessagesBase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace Utopia\Messaging\Adapter;

/**
* Trait for Vonage Messages API adapters.
*
* Provides common functionality for adapters using the Vonage Messages API (V1).
* This trait can be used by different message type adapters (SMS, Chat, etc.).
*
* Required properties (from extending class):
* - $apiKey: string
* - $apiSecret: string
*
* Reference: https://developer.vonage.com/en/api/messages
*/
trait VonageMessagesBase
{
/**
* Get the API endpoint for the Vonage Messages API.
*/
protected function getApiEndpoint(): string
{
return 'https://api.vonage.com/v1/messages';
}

/**
* Get the authorization header value for the API request.
*
* @todo Implement JWT authentication for non-SMS channels
*/
protected function getAuthorizationHeader(): string
{
return 'Basic ' . \base64_encode("{$this->apiKey}:{$this->apiSecret}");
}

/**
* Build the request headers for the Messages API.
*
* @return array<string>
*/
protected function getRequestHeaders(): array
{
return [
"Authorization: {$this->getAuthorizationHeader()}",
'Content-Type: application/json',
'Accept: application/json',
];
}
}
69 changes: 69 additions & 0 deletions tests/Messaging/Adapter/SMS/VonageMessagesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace Utopia\Tests\Adapter\SMS;

use Utopia\Messaging\Adapter\SMS\VonageMessages;
use Utopia\Messaging\Messages\SMS;
use Utopia\Tests\Adapter\Base;

class VonageMessagesTest extends Base
{
/**
* @throws \Exception
*/
public function testSendSMS(): void
{
$apiKey = \getenv('VONAGE_API_KEY');
$apiSecret = \getenv('VONAGE_API_SECRET');
$to = \getenv('VONAGE_TO');

if (!$apiKey || !$apiSecret || !$to) {
$this->markTestSkipped('Vonage Messages credentials or recipient are not available.');
}

$sender = new VonageMessages(
apiKey: $apiKey,
apiSecret: $apiSecret,
from: \getenv('VONAGE_FROM') ?: 'Vonage',
);

$message = new SMS(
to: [$to],
content: 'Test Content',
);

$response = $sender->send($message);

$this->assertResponse($response);
}

/**
* @throws \Exception
*/
public function testSendSMSWithFallbackFrom(): void
{
$apiKey = \getenv('VONAGE_API_KEY');
$apiSecret = \getenv('VONAGE_API_SECRET');
$to = \getenv('VONAGE_TO');
$from = \getenv('VONAGE_FROM') ?: null;

if (!$apiKey || !$apiSecret || !$to || !$from) {
$this->markTestSkipped('Vonage Messages credentials or sender/recipient are not available.');
}

$sender = new VonageMessages(
apiKey: $apiKey,
apiSecret: $apiSecret,
);

$message = new SMS(
to: [$to],
content: 'Test Content',
from: $from,
);

$response = $sender->send($message);

$this->assertResponse($response);
}
}
Loading