From ba22934e7cc268fd0e8a496200271451988f0539 Mon Sep 17 00:00:00 2001 From: deepshekhardas Date: Mon, 16 Mar 2026 18:00:41 +0530 Subject: [PATCH] feat: add Vonage multi-channel Messages adapter (#8139) --- src/Utopia/Messaging/Adapter/MMS.php | 32 +++++++ src/Utopia/Messaging/Adapter/MMS/Vonage.php | 33 +++++++ .../Messaging/Adapter/SMS/VonageMessages.php | 34 +++++++ src/Utopia/Messaging/Adapter/Viber.php | 32 +++++++ src/Utopia/Messaging/Adapter/Viber/Vonage.php | 33 +++++++ src/Utopia/Messaging/Adapter/VonageTrait.php | 72 ++++++++++++++ src/Utopia/Messaging/Adapter/WhatsApp.php | 32 +++++++ .../Messaging/Adapter/WhatsApp/Vonage.php | 33 +++++++ .../Adapter/SMS/VonageMessagesTest.php | 93 +++++++++++++++++++ 9 files changed, 394 insertions(+) create mode 100644 src/Utopia/Messaging/Adapter/MMS.php create mode 100644 src/Utopia/Messaging/Adapter/MMS/Vonage.php create mode 100644 src/Utopia/Messaging/Adapter/SMS/VonageMessages.php create mode 100644 src/Utopia/Messaging/Adapter/Viber.php create mode 100644 src/Utopia/Messaging/Adapter/Viber/Vonage.php create mode 100644 src/Utopia/Messaging/Adapter/VonageTrait.php create mode 100644 src/Utopia/Messaging/Adapter/WhatsApp.php create mode 100644 src/Utopia/Messaging/Adapter/WhatsApp/Vonage.php create mode 100644 tests/Messaging/Adapter/SMS/VonageMessagesTest.php diff --git a/src/Utopia/Messaging/Adapter/MMS.php b/src/Utopia/Messaging/Adapter/MMS.php new file mode 100644 index 0000000..5f63a6c --- /dev/null +++ b/src/Utopia/Messaging/Adapter/MMS.php @@ -0,0 +1,32 @@ +>} + * + * @throws \Exception If the message fails. + */ + abstract protected function process(SMSMessage $message): array; +} diff --git a/src/Utopia/Messaging/Adapter/MMS/Vonage.php b/src/Utopia/Messaging/Adapter/MMS/Vonage.php new file mode 100644 index 0000000..6102a7e --- /dev/null +++ b/src/Utopia/Messaging/Adapter/MMS/Vonage.php @@ -0,0 +1,33 @@ +processMessage($message, 'mms'); + } +} diff --git a/src/Utopia/Messaging/Adapter/SMS/VonageMessages.php b/src/Utopia/Messaging/Adapter/SMS/VonageMessages.php new file mode 100644 index 0000000..487e964 --- /dev/null +++ b/src/Utopia/Messaging/Adapter/SMS/VonageMessages.php @@ -0,0 +1,34 @@ +processMessage($message, 'sms'); + } +} diff --git a/src/Utopia/Messaging/Adapter/Viber.php b/src/Utopia/Messaging/Adapter/Viber.php new file mode 100644 index 0000000..8061da0 --- /dev/null +++ b/src/Utopia/Messaging/Adapter/Viber.php @@ -0,0 +1,32 @@ +>} + * + * @throws \Exception If the message fails. + */ + abstract protected function process(SMSMessage $message): array; +} diff --git a/src/Utopia/Messaging/Adapter/Viber/Vonage.php b/src/Utopia/Messaging/Adapter/Viber/Vonage.php new file mode 100644 index 0000000..f6b14e6 --- /dev/null +++ b/src/Utopia/Messaging/Adapter/Viber/Vonage.php @@ -0,0 +1,33 @@ +processMessage($message, 'viber'); + } +} diff --git a/src/Utopia/Messaging/Adapter/VonageTrait.php b/src/Utopia/Messaging/Adapter/VonageTrait.php new file mode 100644 index 0000000..648a01d --- /dev/null +++ b/src/Utopia/Messaging/Adapter/VonageTrait.php @@ -0,0 +1,72 @@ + $this->from ?? $message->getFrom(), + 'to' => \ltrim($message->getTo()[0], '+'), + 'message_type' => 'text', + 'text' => $message->getContent(), + 'channel' => $channel, + ]; + + $jwt = JWT::encode( + [ + 'application_id' => $this->applicationId, + 'iat' => \time(), + 'jti' => \bin2hex(\random_bytes(16)), + 'exp' => \time() + 3600, + ], + $this->privateKey, + 'RS256' + ); + + $response = new Response($this->getType()); + $result = $this->request( + method: 'POST', + url: 'https://api.nexmo.com/v1/messages', + headers: [ + 'Content-Type: application/json', + 'Authorization: Bearer ' . $jwt, + 'Accept: application/json', + ], + body: $payload, + ); + + $statusCode = $result['statusCode']; + $res = $result['response']; + + if ($statusCode >= 200 && $statusCode < 300 && isset($res['message_uuid'])) { + $response->setDeliveredTo(1); + $response->addResult($message->getTo()[0]); + } else { + $error = $res['detail'] ?? $res['title'] ?? 'Unknown error'; + $response->addResult($message->getTo()[0], $error); + } + + return $response->toArray(); + } +} diff --git a/src/Utopia/Messaging/Adapter/WhatsApp.php b/src/Utopia/Messaging/Adapter/WhatsApp.php new file mode 100644 index 0000000..5a817bf --- /dev/null +++ b/src/Utopia/Messaging/Adapter/WhatsApp.php @@ -0,0 +1,32 @@ +>} + * + * @throws \Exception If the message fails. + */ + abstract protected function process(SMSMessage $message): array; +} diff --git a/src/Utopia/Messaging/Adapter/WhatsApp/Vonage.php b/src/Utopia/Messaging/Adapter/WhatsApp/Vonage.php new file mode 100644 index 0000000..e1d9138 --- /dev/null +++ b/src/Utopia/Messaging/Adapter/WhatsApp/Vonage.php @@ -0,0 +1,33 @@ +processMessage($message, 'whatsapp'); + } +} diff --git a/tests/Messaging/Adapter/SMS/VonageMessagesTest.php b/tests/Messaging/Adapter/SMS/VonageMessagesTest.php new file mode 100644 index 0000000..a861989 --- /dev/null +++ b/tests/Messaging/Adapter/SMS/VonageMessagesTest.php @@ -0,0 +1,93 @@ +applicationId, $this->privateKey); + $sender->setEndpoint('http://request-catcher-sms:5000/'); + + $message = new SMS( + to: ['+1234567890'], + content: 'Test SMS Content', + from: 'Appwrite' + ); + + $response = $sender->send($message); + $result = \json_decode($response, true); + + // This assumes the mock server returns a success response + // In a real environment, we'd check the request-catcher data + $this->assertNotEmpty($result); + } + + public function testSendWhatsApp(): void + { + $sender = new VonageWhatsApp($this->applicationId, $this->privateKey); + $sender->setEndpoint('http://request-catcher-whatsapp:5000/'); + + $message = new SMS( + to: ['+1234567890'], + content: 'Test WhatsApp Content', + from: 'Appwrite' + ); + + $response = $sender->send($message); + $result = \json_decode($response, true); + + $this->assertNotEmpty($result); + } + + public function testSendViber(): void + { + $sender = new VonageViber($this->applicationId, $this->privateKey); + $sender->setEndpoint('http://request-catcher-viber:5000/'); + + $message = new SMS( + to: ['+1234567890'], + content: 'Test Viber Content', + from: 'Appwrite' + ); + + $response = $sender->send($message); + $result = \json_decode($response, true); + + $this->assertNotEmpty($result); + } + + public function testSendMMS(): void + { + $sender = new VonageMMS($this->applicationId, $this->privateKey); + $sender->setEndpoint('http://request-catcher-mms:5000/'); + + $message = new SMS( + to: ['+1234567890'], + content: 'Test MMS Content', + from: 'Appwrite' + ); + + $response = $sender->send($message); + $result = \json_decode($response, true); + + $this->assertNotEmpty($result); + } +}