Skip to content

feat: add Telnyx SMS adapter (#7811)#108

Open
deepshekhardas wants to merge 1 commit into
utopia-php:mainfrom
deepshekhardas:feat/7811-telnyx-adapter
Open

feat: add Telnyx SMS adapter (#7811)#108
deepshekhardas wants to merge 1 commit into
utopia-php:mainfrom
deepshekhardas:feat/7811-telnyx-adapter

Conversation

@deepshekhardas

@deepshekhardas deepshekhardas commented Mar 16, 2026

Copy link
Copy Markdown

Updates the Telnyx SMS adapter.

Changes

  • Refactored \Telnyx.php\ SMS adapter
  • Updated corresponding tests

Testing

  • Tests updated to match refactored adapter

Closes #7811

@coderabbitai

coderabbitai Bot commented Mar 16, 2026

Copy link
Copy Markdown

Walkthrough

The Telnyx SMS adapter's constructor parameter $from was changed from nullable to required non-nullable. The process() method was refactored to send individual API requests for each recipient instead of a single batch request. Request construction now uses string interpolation for headers and includes per-recipient values for from, to, and text fields. Delivery reporting and error handling were updated to track metrics per recipient. The corresponding test file was updated to enable the Telnyx sender, construct actual SMS objects using environment variables, and execute assertions instead of placeholder code.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title 'feat: add Telnyx SMS adapter' clearly and accurately summarizes the main change: adding a new SMS adapter for the Telnyx service provider.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description check ✅ Passed The pull request description accurately describes the changeset: refactored Telnyx SMS adapter and updated corresponding tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/Messaging/Adapter/SMS/TelnyxTest.php`:
- Around line 16-22: The test fails because Telnyx is instantiated with too few
arguments and doesn't guard missing env vars; update the test in TelnyxTest to
first read and validate getenv('TELNYX_API_KEY'), getenv('TELNYX_TO') and
getenv('TELNYX_FROM') and call markTestSkipped (or return) if any are empty,
then construct the Telnyx sender passing the required from argument (e.g.,
include the from value when calling new Telnyx(...)) and create the SMS using
the validated env values before calling send.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ea64e157-ee53-4fa5-baaf-f297eaae6463

📥 Commits

Reviewing files that changed from the base of the PR and between fcb4c3c and e416217.

📒 Files selected for processing (2)
  • src/Utopia/Messaging/Adapter/SMS/Telnyx.php
  • tests/Messaging/Adapter/SMS/TelnyxTest.php

Comment on lines +16 to +22
$sender = new Telnyx(\getenv('TELNYX_API_KEY'));

// $message = new SMS(
// to: ['+18034041123'],
// content: 'Test Content',
// from: '+15005550006'
// );
$message = new SMS(
to: [\getenv('TELNYX_TO')],
content: 'Test Content',
from: \getenv('TELNYX_FROM')
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Pass the required Telnyx from argument and guard missing env vars.

This test currently instantiates Telnyx with too few arguments, which will fail before the send call runs. It also should skip cleanly when Telnyx env vars are not configured.

Proposed patch
 public function testSendSMS(): void
 {
-    $sender = new Telnyx(\getenv('TELNYX_API_KEY'));
+    $apiKey = \getenv('TELNYX_API_KEY') ?: null;
+    $from = \getenv('TELNYX_FROM') ?: null;
+    $to = \getenv('TELNYX_TO') ?: null;
+
+    if (!$apiKey || !$from || !$to) {
+        $this->markTestSkipped('TELNYX_API_KEY, TELNYX_FROM, and TELNYX_TO are required for this integration test.');
+    }
+
+    $sender = new Telnyx($apiKey, $from);
 
     $message = new SMS(
-        to: [\getenv('TELNYX_TO')],
+        to: [$to],
         content: 'Test Content',
-        from: \getenv('TELNYX_FROM')
+        from: $from
     );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$sender = new Telnyx(\getenv('TELNYX_API_KEY'));
// $message = new SMS(
// to: ['+18034041123'],
// content: 'Test Content',
// from: '+15005550006'
// );
$message = new SMS(
to: [\getenv('TELNYX_TO')],
content: 'Test Content',
from: \getenv('TELNYX_FROM')
);
$apiKey = \getenv('TELNYX_API_KEY') ?: null;
$from = \getenv('TELNYX_FROM') ?: null;
$to = \getenv('TELNYX_TO') ?: null;
if (!$apiKey || !$from || !$to) {
$this->markTestSkipped('TELNYX_API_KEY, TELNYX_FROM, and TELNYX_TO are required for this integration test.');
}
$sender = new Telnyx($apiKey, $from);
$message = new SMS(
to: [$to],
content: 'Test Content',
from: $from
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Messaging/Adapter/SMS/TelnyxTest.php` around lines 16 - 22, The test
fails because Telnyx is instantiated with too few arguments and doesn't guard
missing env vars; update the test in TelnyxTest to first read and validate
getenv('TELNYX_API_KEY'), getenv('TELNYX_TO') and getenv('TELNYX_FROM') and call
markTestSkipped (or return) if any are empty, then construct the Telnyx sender
passing the required from argument (e.g., include the from value when calling
new Telnyx(...)) and create the SMS using the validated env values before
calling send.

@deepshekhardas deepshekhardas force-pushed the feat/7811-telnyx-adapter branch from e416217 to 4621f3b Compare June 2, 2026 01:55
@deepshekhardas

Copy link
Copy Markdown
Author

Rebased onto latest main. Could you please review? 🙏

@greptile-apps

greptile-apps Bot commented Jun 2, 2026

Copy link
Copy Markdown

Greptile Summary

This PR refactors the Telnyx SMS adapter by making $from a required constructor parameter and updating the process() loop to extract error details from the API response. The test is substantially rewritten to uncomment the previously skipped live-send flow.

  • The adapter now requires $from in the constructor and drops $this->from ?? $message->getFrom(), meaning $message->getFrom() is silently ignored — diverging from Twilio, Sinch, and every other adapter in this codebase that supports a message-level from override.
  • The test passes TELNYX_FROM as the from field on the SMS message (where the adapter never reads it) instead of as the second argument to new Telnyx(...), so the env var has no effect and the outgoing from in the API call is undefined.

Confidence Score: 3/5

The test will not pass as written, and the adapter removes a fallback that callers in this codebase rely on.

The test incorrectly routes TELNYX_FROM to the SMS message object (where it is ignored) rather than the Telnyx constructor, and the adapter now silently discards $message->getFrom() in a way that contradicts every other SMS adapter in the repo. Both issues need to be corrected before the PR is merge-ready.

Both files need attention: TelnyxTest.php has the wrong constructor call and misrouted env var, and Telnyx.php drops the $message->getFrom() fallback without a clear justification given the established pattern.

Important Files Changed

Filename Overview
src/Utopia/Messaging/Adapter/SMS/Telnyx.php Refactored adapter makes $from required and removes the $message->getFrom() fallback, diverging from every other adapter in the codebase; error detail extraction and foreach structure also changed.
tests/Messaging/Adapter/SMS/TelnyxTest.php Test is broken in multiple ways: passes only one arg to a two-arg constructor, routes TELNYX_FROM to the SMS message (where it is ignored) instead of the Telnyx constructor, and runs a live network request without a skip guard.

Reviews (3): Last reviewed commit: "feat: add Telnyx SMS adapter (#7811)" | Re-trigger Greptile

Comment on lines +16 to +22
$sender = new Telnyx(\getenv('TELNYX_API_KEY'));

// $message = new SMS(
// to: ['+18034041123'],
// content: 'Test Content',
// from: '+15005550006'
// );
$message = new SMS(
to: [\getenv('TELNYX_TO')],
content: 'Test Content',
from: \getenv('TELNYX_FROM')
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 The Telnyx constructor now requires two arguments ($apiKey and $from), but only one is passed here. This will throw a PHP fatal error — "Too few arguments to function Telnyx::__construct(), 1 passed and exactly 2 expected" — making the entire test suite fail immediately. The TELNYX_FROM env var is already used for the SMS message, but needs to be passed to the adapter constructor as well.

Suggested change
$sender = new Telnyx(\getenv('TELNYX_API_KEY'));
// $message = new SMS(
// to: ['+18034041123'],
// content: 'Test Content',
// from: '+15005550006'
// );
$message = new SMS(
to: [\getenv('TELNYX_TO')],
content: 'Test Content',
from: \getenv('TELNYX_FROM')
);
$sender = new Telnyx(\getenv('TELNYX_API_KEY'), \getenv('TELNYX_FROM'));
$message = new SMS(
to: [\getenv('TELNYX_TO')],
content: 'Test Content',
);

Comment on lines +46 to 67
foreach ($message->getTo() as $to) {
$result = $this->request(
method: 'POST',
url: 'https://api.telnyx.com/v2/messages',
headers: [
'Content-Type: application/json',
"Authorization: Bearer {$this->apiKey}",
],
body: [
'from' => $this->from,
'to' => $to,
'text' => $message->getContent(),
]
);

if ($result['statusCode'] >= 200 && $result['statusCode'] < 300) {
$response->setDeliveredTo(\count($message->getTo()));
foreach ($message->getTo() as $to) {
if ($result['statusCode'] >= 200 && $result['statusCode'] < 300) {
$response->incrementDeliveredTo();
$response->addResult($to);
}
} else {
foreach ($message->getTo() as $to) {
$response->addResult($to, 'Unknown error.');
} else {
$response->addResult($to, $result['response']['errors'][0]['detail'] ?? 'Unknown error');
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Since getMaxMessagesPerRequest() returns 1, the parent send() method enforces that process() is always called with exactly one recipient and throws an exception otherwise. The foreach loop will therefore always iterate exactly once, making it misleading and inconsistent with the Twilio adapter (which uses $message->getTo()[0] directly). Accessing the single recipient directly is clearer and aligns with how other single-recipient adapters in this codebase are implemented.

Suggested change
foreach ($message->getTo() as $to) {
$result = $this->request(
method: 'POST',
url: 'https://api.telnyx.com/v2/messages',
headers: [
'Content-Type: application/json',
"Authorization: Bearer {$this->apiKey}",
],
body: [
'from' => $this->from,
'to' => $to,
'text' => $message->getContent(),
]
);
if ($result['statusCode'] >= 200 && $result['statusCode'] < 300) {
$response->setDeliveredTo(\count($message->getTo()));
foreach ($message->getTo() as $to) {
if ($result['statusCode'] >= 200 && $result['statusCode'] < 300) {
$response->incrementDeliveredTo();
$response->addResult($to);
}
} else {
foreach ($message->getTo() as $to) {
$response->addResult($to, 'Unknown error.');
} else {
$response->addResult($to, $result['response']['errors'][0]['detail'] ?? 'Unknown error');
}
}
$to = $message->getTo()[0];
$result = $this->request(
method: 'POST',
url: 'https://api.telnyx.com/v2/messages',
headers: [
'Content-Type: application/json',
"Authorization: Bearer {$this->apiKey}",
],
body: [
'from' => $this->from,
'to' => $to,
'text' => $message->getContent(),
]
);
if ($result['statusCode'] >= 200 && $result['statusCode'] < 300) {
$response->setDeliveredTo(1);
$response->addResult($to);
} else {
$response->addResult($to, $result['response']['errors'][0]['detail'] ?? 'Unknown error');
}

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@deepshekhardas deepshekhardas force-pushed the feat/7811-telnyx-adapter branch from 4621f3b to 150212f Compare June 17, 2026 09:20
"Authorization: Bearer {$this->apiKey}",
],
body: [
'from' => $this->from,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Profile IDs are misplaced

The constructor now documents $from as either a Telnyx phone number or profile ID, but this request always sends that value in the from field. Telnyx uses a separate messaging_profile_id field for profile IDs, so a caller that passes a profile UUID as allowed by this adapter will send an invalid payload and the SMS will fail.

);

// $result = $sender->send($message);
$result = $sender->send($message);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Live send runs by default

This test now performs a real Telnyx send in the default PHPUnit suite. In normal CI or local runs without Telnyx credentials, test numbers, and network access, this path will fail or attempt to send a real SMS. The previous test was skipped for this reason, so this should stay gated on available credentials or use a mocked/request-catcher flow.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@deepshekhardas deepshekhardas force-pushed the feat/7811-telnyx-adapter branch from 150212f to d215ba9 Compare July 10, 2026 05:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant