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
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
use Utopia\Messaging\Messages\SMS;
use Utopia\Messaging\Response;

class Vonage extends SMSAdapter
class VonageLegacy extends SMSAdapter
{
Comment thread
bhardwajparth51 marked this conversation as resolved.
protected const NAME = 'Vonage';
protected const NAME = 'Vonage Legacy';

/**
* @param string $apiKey Vonage API Key
Expand Down
110 changes: 110 additions & 0 deletions src/Utopia/Messaging/Adapter/SMS/VonageMessages.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

namespace Utopia\Messaging\Adapter\SMS;

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

// Vonage Messages API SMS Adapter.
// This adapter uses the modern Vonage Messages API (V1) which is more cost-effective
// and versatile than the legacy SMS API.
// https://developer.vonage.com/en/api/messages
class VonageMessages extends SMSAdapter
{
protected const NAME = 'Vonage Messages';

public function __construct(
private string $apiKey,
private string $apiSecret,
private ?string $from = null
) {
}
Comment thread
bhardwajparth51 marked this conversation as resolved.

// Vonage Messages API endpoint.
protected function getApiEndpoint(): string
{
return 'https://api.vonage.com/v1/messages';
}

// Generates the Basic Authorization header.
protected function getAuthorizationHeader(): string
{
return 'Basic ' . \base64_encode("{$this->apiKey}:{$this->apiSecret}");
}

/**
* @return array<string>
*/
protected function getRequestHeaders(): array
{
return [
"Authorization: {$this->getAuthorizationHeader()}",
'Content-Type: application/json',
'Accept: application/json',
'User-Agent: Utopia Messaging',
];
}

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

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

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(),
Comment thread
greptile-apps[bot] marked this conversation as resolved.
'channel' => 'sms',
],
);

if ($result['statusCode'] === 202) {
$response->setDeliveredTo(1);
$response->addResult($message->getTo()[0]);
} else {
$errorMessage = "Error {$result['statusCode']}";

if (\is_array($result['response'])) {
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'];
} elseif (\is_string($result['response']) && !empty($result['response'])) {
$errorMessage = "Error {$result['statusCode']}: " . \mb_strimwidth(\strip_tags($result['response']), 0, 100, '...');
}

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

return $response->toArray();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

namespace Utopia\Tests\Adapter\SMS;

use Utopia\Messaging\Adapter\SMS\VonageLegacy;
use Utopia\Tests\Adapter\Base;

class VonageTest extends Base
class VonageLegacyTest extends Base
{
/**
* @throws \Exception
Expand All @@ -17,7 +18,7 @@ public function testSendSMS(): void
$apiKey = \getenv('VONAGE_API_KEY');
$apiSecret = \getenv('VONAGE_API_SECRET');

$sender = new Vonage($apiKey, $apiSecret);
$sender = new VonageLegacy($apiKey, $apiSecret);

$message = new SMS(
to: [\getenv('VONAGE_TO')],
Expand Down
65 changes: 65 additions & 0 deletions tests/Messaging/Adapter/SMS/VonageMessagesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?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
{
// Tests sending an SMS with the 'from' number set directly in the adapter.
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);
}

// Tests sending an SMS where the 'from' number is provided by the message object.
public function testSendSMSWithFallbackFrom(): void
{
$apiKey = \getenv('VONAGE_API_KEY');
Comment thread
ChiragAgg5k marked this conversation as resolved.
$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