From 2371a7ac2749e1659d805734bc6f3d9cf4ce5f91 Mon Sep 17 00:00:00 2001 From: deepshekhardas Date: Sun, 14 Jun 2026 10:39:43 +0530 Subject: [PATCH 1/2] feat: add Vonage Messages API adapter Renames the existing Vonage adapter to VonageLegacy and adds a new VonageMessages adapter using the modern Vonage Messages API (V1). Based on PR #111 by bhardwajparth51. --- .../Messaging/Adapter/SMS/VonageLegacy.php | 76 +++++++++++++ .../Messaging/Adapter/SMS/VonageMessages.php | 102 ++++++++++++++++++ .../Adapter/SMS/VonageLegacyTest.php | 35 ++++++ .../Adapter/SMS/VonageMessagesTest.php | 36 +++++++ 4 files changed, 249 insertions(+) create mode 100644 src/Utopia/Messaging/Adapter/SMS/VonageLegacy.php create mode 100644 src/Utopia/Messaging/Adapter/SMS/VonageMessages.php create mode 100644 tests/Messaging/Adapter/SMS/VonageLegacyTest.php create mode 100644 tests/Messaging/Adapter/SMS/VonageMessagesTest.php diff --git a/src/Utopia/Messaging/Adapter/SMS/VonageLegacy.php b/src/Utopia/Messaging/Adapter/SMS/VonageLegacy.php new file mode 100644 index 00000000..5e75618f --- /dev/null +++ b/src/Utopia/Messaging/Adapter/SMS/VonageLegacy.php @@ -0,0 +1,76 @@ + \ltrim($to, '+'), + $message->getTo() + ); + + $response = new Response($this->getType()); + $result = $this->request( + method: 'POST', + url: 'https://rest.nexmo.com/sms/json', + headers: [ + 'Content-Type' => 'application/x-www-form-urlencoded', + ], + body: [ + 'text' => $message->getContent(), + 'from' => $this->from ?? $message->getFrom(), + 'to' => $to[0], //\implode(',', $to), + 'api_key' => $this->apiKey, + 'api_secret' => $this->apiSecret, + ], + ); + + if (($result['response']['messages'][0]['status'] ?? null) === 0) { + $response->setDeliveredTo(1); + $response->addResult($result['response']['messages'][0]['to']); + } else { + if (!\is_null($result['response']['messages'][0]['error-text'] ?? null)) { + $response->addResult($message->getTo()[0], $result['response']['messages'][0]['error-text']); + } else { + $response->addResult($message->getTo()[0], 'Unknown error'); + } + } + + return $response->toArray(); + } +} diff --git a/src/Utopia/Messaging/Adapter/SMS/VonageMessages.php b/src/Utopia/Messaging/Adapter/SMS/VonageMessages.php new file mode 100644 index 00000000..61c43dfe --- /dev/null +++ b/src/Utopia/Messaging/Adapter/SMS/VonageMessages.php @@ -0,0 +1,102 @@ +apiKey}:{$this->apiSecret}"); + } + + /** + * @return array + */ + protected function getRequestHeaders(): array + { + return [ + "Authorization: {$this->getAuthorizationHeader()}", + 'Content-Type: application/json', + 'Accept: application/json', + 'User-Agent: Utopia Messaging', + ]; + } + + public function getName(): string + { + return static::NAME; + } + + 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(), + '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(); + } +} diff --git a/tests/Messaging/Adapter/SMS/VonageLegacyTest.php b/tests/Messaging/Adapter/SMS/VonageLegacyTest.php new file mode 100644 index 00000000..2a9dcfca --- /dev/null +++ b/tests/Messaging/Adapter/SMS/VonageLegacyTest.php @@ -0,0 +1,35 @@ +markTestSkipped('Vonage credentials are not available.'); + + /* + $apiKey = \getenv('VONAGE_API_KEY'); + $apiSecret = \getenv('VONAGE_API_SECRET'); + + $sender = new Vonage($apiKey, $apiSecret); + + $message = new SMS( + to: [\getenv('VONAGE_TO')], + content: 'Test Content', + from: \getenv('VONAGE_FROM') + ); + + $response = $sender->send($message); + + $result = \json_decode($response, true); + + $this->assertResponse($result); + */ + } +} diff --git a/tests/Messaging/Adapter/SMS/VonageMessagesTest.php b/tests/Messaging/Adapter/SMS/VonageMessagesTest.php new file mode 100644 index 00000000..b956b3f1 --- /dev/null +++ b/tests/Messaging/Adapter/SMS/VonageMessagesTest.php @@ -0,0 +1,36 @@ +markTestSkipped('Vonage Messages credentials are not available.'); + + /* + $apiKey = \getenv('VONAGE_MESSAGES_API_KEY'); + $apiSecret = \getenv('VONAGE_MESSAGES_API_SECRET'); + + $sender = new VonageMessages($apiKey, $apiSecret); + + $message = new SMS( + to: [\getenv('VONAGE_MESSAGES_TO')], + content: 'Test Content', + from: \getenv('VONAGE_MESSAGES_FROM'), + ); + + $response = $sender->send($message); + + $this->assertNotEmpty($response['results']); + $this->assertNotEmpty($response['results'][0]['success']); + */ + } +} From c039ea67f4a55a227c71abc07e94a4ab68005e71 Mon Sep 17 00:00:00 2001 From: deepshekhardas Date: Sun, 14 Jun 2026 10:41:10 +0530 Subject: [PATCH 2/2] chore: remove old Vonage files after rename --- src/Utopia/Messaging/Adapter/SMS/Vonage.php | 77 --------------------- tests/Messaging/Adapter/SMS/VonageTest.php | 35 ---------- 2 files changed, 112 deletions(-) delete mode 100644 src/Utopia/Messaging/Adapter/SMS/Vonage.php delete mode 100644 tests/Messaging/Adapter/SMS/VonageTest.php diff --git a/src/Utopia/Messaging/Adapter/SMS/Vonage.php b/src/Utopia/Messaging/Adapter/SMS/Vonage.php deleted file mode 100644 index d583104c..00000000 --- a/src/Utopia/Messaging/Adapter/SMS/Vonage.php +++ /dev/null @@ -1,77 +0,0 @@ - \ltrim($to, '+'), - $message->getTo() - ); - - $response = new Response($this->getType()); - $result = $this->request( - method: 'POST', - url: 'https://rest.nexmo.com/sms/json', - headers: [ - 'Content-Type' => 'application/x-www-form-urlencoded', - ], - body: [ - 'text' => $message->getContent(), - 'from' => $this->from ?? $message->getFrom(), - 'to' => $to[0], //\implode(',', $to), - 'api_key' => $this->apiKey, - 'api_secret' => $this->apiSecret, - ], - ); - - if (($result['response']['messages'][0]['status'] ?? null) === 0) { - $response->setDeliveredTo(1); - $response->addResult($result['response']['messages'][0]['to']); - } else { - if (!\is_null($result['response']['messages'][0]['error-text'] ?? null)) { - $response->addResult($message->getTo()[0], $result['response']['messages'][0]['error-text']); - } else { - $response->addResult($message->getTo()[0], 'Unknown error'); - } - } - - return $response->toArray(); - } -} diff --git a/tests/Messaging/Adapter/SMS/VonageTest.php b/tests/Messaging/Adapter/SMS/VonageTest.php deleted file mode 100644 index 67027be9..00000000 --- a/tests/Messaging/Adapter/SMS/VonageTest.php +++ /dev/null @@ -1,35 +0,0 @@ -markTestSkipped('Vonage credentials are not available.'); - - /* - $apiKey = \getenv('VONAGE_API_KEY'); - $apiSecret = \getenv('VONAGE_API_SECRET'); - - $sender = new Vonage($apiKey, $apiSecret); - - $message = new SMS( - to: [\getenv('VONAGE_TO')], - content: 'Test Content', - from: \getenv('VONAGE_FROM') - ); - - $response = $sender->send($message); - - $result = \json_decode($response, true); - - $this->assertResponse($result); - */ - } -}