Skip to content

feat(providers): add text.lk sms provider integration#9975

Open
ritambh888 wants to merge 10 commits intonovuhq:nextfrom
ritambh888:feature/add-textlk-provider
Open

feat(providers): add text.lk sms provider integration#9975
ritambh888 wants to merge 10 commits intonovuhq:nextfrom
ritambh888:feature/add-textlk-provider

Conversation

@ritambh888
Copy link

Description

Implemented the Text.lk SMS provider integration as requested in issue #9725.

Changes

  • Added TextLkSmsProvider implementation using axios.
  • Added unit tests using vitest mocking.
  • Registered the provider in the SMS factory.

Related Issue

Closes #9725

@netlify
Copy link

netlify bot commented Feb 4, 2026

👷 Deploy request for dashboard-v2-novu-staging pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit c19827b

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 4, 2026

Walkthrough

Adds an exported TextLkSmsProvider class implementing ISmsProvider with id textlk and channelType SMS. Constructor accepts { apiKey }. sendMessage normalizes the recipient, builds payload (recipient, sender_id default Text.lk, type 'plain', message), POSTs JSON to https://app.text.lk/api/v3/sms/send with Authorization: Bearer <apiKey> and a 30s timeout, and returns { id: response.data.uid || timestamp, date: ISO string }. Adds unit tests that mock axios.post and verify request formation and response handling.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The implementation meets the core requirements: API key authentication, sendMessage functionality, and proper success response handling. However, sender ID configuration and bulk SMS sending support are not evident in the provided code. Clarify whether sender ID configuration flexibility and bulk SMS support were intended as part of this PR or deferred to future work. The current implementation uses a hardcoded default sender_id.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a Text.lk SMS provider integration as a new feature.
Description check ✅ Passed The description is directly related to the changeset, mentioning the TextLkSmsProvider implementation, unit tests, factory registration, and linking to issue #9725.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the Text.lk SMS provider integration. The test file and provider implementation are both in-scope additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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


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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/providers/src/lib/sms/textlk/textlk.provider.ts`:
- Around line 24-29: The payload currently hard-codes sender_id to 'Text.lk'
which prevents per-message sender customization; update the payload construction
in textlk.provider.ts (where payload is built) to use options.from if present
(falling back to 'Text.lk' as default) so the sender_id supports
ISmsOptions.from values (ensure it accepts alphanumeric up to 11 chars or
phone-format as the API expects) and preserve recipient/message fields as-is.
🧹 Nitpick comments (2)
packages/providers/src/lib/sms/textlk/textlk.provider.spec.ts (1)

4-5: Tighten assertions and drop trivial comments.

Capture the response to validate mapping, and remove inline comments that don’t add value.

🧪 Suggested update
-// Mock axios so we don't actually send SMS during tests
 const mockPost = vi.fn();
@@
-    await provider.sendMessage({
+    const result = await provider.sendMessage({
       to: '+94771234567',
       content: 'Hello World',
     });
 
-    // Check if axios.post was called
     expect(mockPost).toHaveBeenCalled();
     
-    // Check if called with the right URL and Payload
     expect(mockPost).toHaveBeenCalledWith(
       'https://app.text.lk/api/v3/sms/send',
       {
         recipient: '+94771234567',
         sender_id: 'Text.lk',
         type: 'plain',
         message: 'Hello World',
       },
       {
         headers: {
           'Authorization': 'Bearer test-api-key',
           'Content-Type': 'application/json',
           'Accept': 'application/json',
         },
       }
     );
+
+    expect(result).toEqual({
+      id: 'mock-uid-123',
+      date: expect.any(String),
+    });

As per coding guidelines, don't leave comments in code, unless they explain something complex and not trivial.

Also applies to: 27-35

packages/providers/src/lib/sms/textlk/textlk.provider.ts (1)

9-17: Refactor to factory function + interface to align with TypeScript style guidelines; note this pattern is used across all SMS providers.

The class-based pattern violates the guideline to use functional and declarative programming patterns. While a factory function refactor is appropriate, note that all ~35 SMS providers in this directory currently use the same class-based structure, so this change should either be applied consistently across all providers or deferred as a broader refactoring initiative.

Additionally, remove the unnecessary comment on line 26 (// Default sender. This can be made configurable later if requested.). It explains something trivial and clutters the code.

@ritambh888 ritambh888 force-pushed the feature/add-textlk-provider branch from 6151266 to 712c94a Compare February 4, 2026 06:28
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/providers/src/lib/sms/textlk/textlk.provider.ts`:
- Around line 24-29: The payload currently uses options.to verbatim; update the
recipient normalization before building payload so Text.lk gets numbers without
leading '+' and handles comma-separated lists. In the function that constructs
payload (where payload and options.to are used), split options.to on commas,
trim each entry, strip any leading '+' from each phone number, then rejoin (or
pass the array) into the recipient field so the API receives numbers in the
94XXXXXXXXX format; ensure this logic is applied where payload is created
(referencing payload and options.to).
🧹 Nitpick comments (4)
packages/providers/src/lib/sms/textlk/textlk.provider.ts (3)

1-1: Consider dash-separated filename.

The filename uses a dot separator; please align with the dash-based naming convention (e.g., textlk-provider.ts) and update imports accordingly. As per coding guidelines, Use lowercase with dashes for directories and files (e.g., components/auth-wizard).


9-17: Prefer a functional provider factory (if the SMS factory allows it).

Guidelines favor functional/declarative patterns over classes; if the provider registry doesn’t require instantiation, consider exporting a factory function that returns an ISmsProvider. As per coding guidelines, Use functional and declarative programming patterns; avoid classes.

♻️ Suggested refactor (if compatible)
-export class TextLkSmsProvider implements ISmsProvider {
-  id = 'textlk';
-  channelType = ChannelTypeEnum.SMS as ChannelTypeEnum.SMS;
-
-  constructor(
-    private config: {
-      apiKey: string;
-    }
-  ) {}
-
-  async sendMessage(
-    options: ISmsOptions
-  ): Promise<ISendMessageSuccessResponse> {
-    const BASE_URL = 'https://app.text.lk/api/v3/sms/send';
-
-    const payload = {
-      recipient: options.to,
-      sender_id: options.from || 'Text.lk',
-      type: 'plain',
-      message: options.content,
-    };
-
-    const response = await axios.post(BASE_URL, payload, {
-      headers: {
-        'Authorization': `Bearer ${this.config.apiKey}`,
-        'Content-Type': 'application/json',
-        'Accept': 'application/json',
-      },
-    });
-
-    return {
-      id: response.data.uid || new Date().getTime().toString(),
-      date: new Date().toISOString(),
-    };
-  }
-}
+export function createTextLkSmsProvider(
+  config: { apiKey: string }
+): ISmsProvider {
+  async function sendMessage(
+    options: ISmsOptions
+  ): Promise<ISendMessageSuccessResponse> {
+    const BASE_URL = 'https://app.text.lk/api/v3/sms/send';
+
+    const payload = {
+      recipient: options.to,
+      sender_id: options.from || 'Text.lk',
+      type: 'plain',
+      message: options.content,
+    };
+
+    const response = await axios.post(BASE_URL, payload, {
+      headers: {
+        'Authorization': `Bearer ${config.apiKey}`,
+        'Content-Type': 'application/json',
+        'Accept': 'application/json',
+      },
+    });
+
+    return {
+      id: response.data.uid || new Date().getTime().toString(),
+      date: new Date().toISOString(),
+    };
+  }
+
+  return {
+    id: 'textlk',
+    channelType: ChannelTypeEnum.SMS as ChannelTypeEnum.SMS,
+    sendMessage,
+  };
+}

13-16: Extract the config shape into an interface.

This keeps the contract reusable and aligns with the “prefer interfaces” rule. As per coding guidelines, Prefer interfaces over types in TypeScript.

♻️ Suggested change
+interface TextLkSmsProviderConfig {
+  apiKey: string;
+}
+
 export class TextLkSmsProvider implements ISmsProvider {
@@
-  constructor(
-    private config: {
-      apiKey: string;
-    }
-  ) {}
+  constructor(private config: TextLkSmsProviderConfig) {}
packages/providers/src/lib/sms/textlk/textlk.provider.spec.ts (1)

1-1: Consider dash-separated spec filename.

Rename to textlk-provider.spec.ts to align with the dash-based naming rule and update imports accordingly. As per coding guidelines, Use lowercase with dashes for directories and files (e.g., components/auth-wizard).

Comment on lines +24 to +29
const payload = {
recipient: options.to,
sender_id: options.from || 'Text.lk',
type: 'plain',
message: options.content,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Text.lk SMS API recipient format documentation E.164

💡 Result:

Text.lk’s SMS API recipient is not strict E.164 with a leading +. Their docs show it as country code + number, digits only:

  • Format: 94XXXXXXXXX (Sri Lanka example: 94712345678) — no leading 0, no +. [1][2]
  • Multiple recipients: comma-separated, e.g. 9476000000,94710000000 (as documented). [1]

So if you have an E.164 number like +94712345678, send it as 94712345678. [2]

Sources:
[1] Text.lk “Send SMS” API docs (recipient examples and comma-separated format)
[2] Text.lk PHP integration guide (“Phone Number Format” correct/incorrect examples)


🏁 Script executed:

# Search for Text.lk provider tests and documentation
find . -type f -name "*textlk*" | head -20

Repository: novuhq/novu

Length of output: 177


🏁 Script executed:

# Search for any documentation or comments about recipient format in the codebase
rg -i "textlk|text\.lk" -A 5 -B 5 | head -100

Repository: novuhq/novu

Length of output: 4929


Normalize recipient phone number for Text.lk API.

Text.lk API requires phone numbers without the leading + prefix in the format 94XXXXXXXXX (country code + digits only). The current implementation passes options.to verbatim; if a +-prefixed E.164 number is provided (as shown in tests), the API will reject it. Remove the + prefix and handle comma-separated numbers if present.

Normalization example
-    const payload = {
-      recipient: options.to,
+    const recipient = options.to
+      .split(',')
+      .map((value) => value.trim().replace(/^\+/, ''))
+      .join(',');
+
+    const payload = {
+      recipient,
       sender_id: options.from || 'Text.lk',
       type: 'plain',
       message: options.content,
     };
📝 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
const payload = {
recipient: options.to,
sender_id: options.from || 'Text.lk',
type: 'plain',
message: options.content,
};
const recipient = options.to
.split(',')
.map((value) => value.trim().replace(/^\+/, ''))
.join(',');
const payload = {
recipient,
sender_id: options.from || 'Text.lk',
type: 'plain',
message: options.content,
};
🤖 Prompt for AI Agents
In `@packages/providers/src/lib/sms/textlk/textlk.provider.ts` around lines 24 -
29, The payload currently uses options.to verbatim; update the recipient
normalization before building payload so Text.lk gets numbers without leading
'+' and handles comma-separated lists. In the function that constructs payload
(where payload and options.to are used), split options.to on commas, trim each
entry, strip any leading '+' from each phone number, then rejoin (or pass the
array) into the recipient field so the API receives numbers in the 94XXXXXXXXX
format; ensure this logic is applied where payload is created (referencing
payload and options.to).

@ritambh888 ritambh888 force-pushed the feature/add-textlk-provider branch from 8de565e to 0200d1d Compare February 4, 2026 06:49
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/providers/src/lib/sms/textlk/textlk.provider.spec.ts`:
- Around line 37-48: The axios.post call (BASE_URL, payload) should be wrapped
in a try-catch to handle network/timeouts and API errors; call axios.post with a
timeout option (e.g., { timeout: ... , headers: {...} }) to avoid hanging,
validate response and response.data before reading response.data.uid (fall back
to a deterministic id if missing), and in the catch block throw or return a
clear, contextual error message that includes the original error and the request
context (BASE_URL/payload) so callers can handle failures gracefully.

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.

🚀 Feature: Add Text.lk SMS Provider Integration

1 participant