Skip to content

feat(providers): add Maqsam WhatsApp chat provider#9463

Open
basseko wants to merge 3 commits intonovuhq:nextfrom
ejari-sa:feat/add-maqsam-whatsapp-chat-provider
Open

feat(providers): add Maqsam WhatsApp chat provider#9463
basseko wants to merge 3 commits intonovuhq:nextfrom
ejari-sa:feat/add-maqsam-whatsapp-chat-provider

Conversation

@basseko
Copy link

@basseko basseko commented Nov 9, 2025

What changed? Why was the change needed?

This PR adds the Maqsam WhatsApp API as an integration (docs: https://portal.mq.maqsam.com/docs/v2/whatsapp)

Screenshots

CleanShot 2025-11-10 at 12 38 48@2x
Expand for optional sections

Related enterprise PR

Special notes for your reviewer

This is separate from the existing Maqsam SMS integration.

@netlify
Copy link

netlify bot commented Nov 9, 2025

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

Visit the deploys page to approve it

Name Link
🔨 Latest commit b3663bb

@basseko basseko force-pushed the feat/add-maqsam-whatsapp-chat-provider branch from 458acac to ba27c15 Compare November 9, 2025 09:06
@github-actions github-actions bot removed the CI/CD label Nov 9, 2025
@basseko basseko force-pushed the feat/add-maqsam-whatsapp-chat-provider branch 2 times, most recently from 969cfbe to 84274a9 Compare November 10, 2025 08:51
@basseko basseko marked this pull request as ready for review November 10, 2025 09:39
@basseko basseko force-pushed the feat/add-maqsam-whatsapp-chat-provider branch from 1ef4fb6 to b3663bb Compare December 28, 2025 11:29
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 28, 2025

Walkthrough

This change introduces support for a new chat provider, Maqsam WhatsApp, throughout the application. It adds an accessSecret credential field to multiple credential-related files and database schemas. A new MaqsamWhatsAppHandler and MaqsamWhatsAppProvider are implemented to handle message delivery via the Maqsam API. The chat factory is updated to register the new handler. Enums, provider configurations, and credential definitions are extended to recognize and support the new provider. Comprehensive test coverage validates the provider's API integration and message-sending functionality.

Pre-merge checks

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(providers): add Maqsam WhatsApp chat provider' directly and accurately summarizes the main objective of the changeset, which is adding a new Maqsam WhatsApp provider integration.
Description check ✅ Passed The description explains that the PR adds Maqsam WhatsApp API as an integration, provides documentation link, includes a screenshot, and clarifies separation from existing Maqsam SMS integration, all of which relate to the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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: 5

🧹 Nitpick comments (3)
libs/application-generic/src/factories/chat/handlers/maqsam.handler.ts (1)

11-11: Remove redundant type annotation.

The explicit type annotation on line 11 is unnecessary since TypeScript can infer the type from the object literal. This simplifies the code without losing type safety.

🔎 Proposed simplification
-  const config: { accessKey: string, accessSecret: string } = { accessKey: credentials.accessKey, accessSecret: credentials.accessSecret };
+  const config = { accessKey: credentials.accessKey, accessSecret: credentials.accessSecret };
packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts (2)

58-58: Consider using server-provided timestamp instead of client-generated date.

The timestamp is generated on the client side using new Date().toISOString(), which could lead to inconsistencies due to clock skew between client and server. If the Maqsam API response includes a timestamp field, prefer using that value for accuracy and consistency.


45-63: Add error handling for network failures.

The axios request is not wrapped in a try-catch block, so network errors, timeouts, or HTTP error status codes will propagate as unhandled exceptions. Consider adding explicit error handling to provide more meaningful error messages.

🔎 Proposed fix with error handling
 async sendMessage(
   options: IChatOptions,
   bridgeProviderData: WithPassthrough<Record<string, unknown>> = {},
 ): Promise<ISendMessageSuccessResponse> {
   const payload = this.transform(bridgeProviderData, this.defineMessagePayload(options));

+  try {
     const { data } = await this.axiosClient.post<ISendMessageRes | ISendMessageFailureRes>(this.baseUrl, 
       payload.body,
     );

     if ('conversationId' in data && data.conversationId) {
       return {
         id: data.conversationId,
         date: new Date().toISOString(),
       };
     }

     throw new Error(`Maqsam Chat failed: ${JSON.stringify(data || {})}`);
+  } catch (error) {
+    if (Axios.isAxiosError(error)) {
+      const responseData = error.response?.data;
+      throw new Error(`Maqsam Chat API error: ${error.message}${responseData ? ` - ${JSON.stringify(responseData)}` : ''}`);
+    }
+    throw error;
+  }
 }
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3dca011 and b3663bb.

⛔ Files ignored due to path filters (1)
  • apps/dashboard/public/images/providers/light/square/maqsam-whatsapp.svg is excluded by !**/*.svg
📒 Files selected for processing (17)
  • apps/api/src/app/integrations/dtos/credentials.dto.ts
  • apps/worker/src/app/workflow/usecases/send-message/send-message-chat.usecase.ts
  • libs/application-generic/src/factories/chat/chat.factory.ts
  • libs/application-generic/src/factories/chat/handlers/maqsam.handler.ts
  • libs/dal/src/repositories/integration/integration.schema.ts
  • libs/internal-sdk/src/models/components/providersidenum.ts
  • packages/framework/src/schemas/providers/chat/index.ts
  • packages/framework/src/shared.ts
  • packages/providers/src/lib/chat/index.ts
  • packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.spec.ts
  • packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts
  • packages/providers/src/lib/chat/maqsam-whatsapp/types/maqsam-whatsapp.types.ts
  • packages/shared/src/consts/providers/channels/chat.ts
  • packages/shared/src/consts/providers/credentials/provider-credentials.ts
  • packages/shared/src/consts/providers/credentials/secure-credentials.ts
  • packages/shared/src/entities/integration/credential.interface.ts
  • packages/shared/src/types/providers.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Write concise, technical TypeScript code with accurate examples
Use descriptive variable names with auxiliary verbs (isLoading, hasError)
Add blank lines before return statements
Import motion components from 'motion/react' instead of 'motion-react'

**/*.{ts,tsx}: Write concise, technical TypeScript code with accurate examples
Use functional and declarative programming patterns; avoid classes
Prefer iteration and modularization over code duplication; minimize code duplication as possible
Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError)
Structure files: exported component, subcomponents, helpers, static content, types
Don't leave comments in code, unless they explain something complex and not trivial
Don't use nested ternaries
Use TypeScript for all code; prefer interfaces over types in backend code, but use types over interfaces in front end code
Use the "function" keyword for pure functions
Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements
Add blank lines before return statements
When importing "motion-react" package, import it from "motion/react"

Files:

  • libs/dal/src/repositories/integration/integration.schema.ts
  • packages/providers/src/lib/chat/index.ts
  • packages/providers/src/lib/chat/maqsam-whatsapp/types/maqsam-whatsapp.types.ts
  • packages/shared/src/entities/integration/credential.interface.ts
  • packages/framework/src/schemas/providers/chat/index.ts
  • packages/shared/src/types/providers.ts
  • packages/framework/src/shared.ts
  • packages/shared/src/consts/providers/credentials/secure-credentials.ts
  • packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts
  • libs/application-generic/src/factories/chat/handlers/maqsam.handler.ts
  • libs/application-generic/src/factories/chat/chat.factory.ts
  • apps/api/src/app/integrations/dtos/credentials.dto.ts
  • apps/worker/src/app/workflow/usecases/send-message/send-message-chat.usecase.ts
  • packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.spec.ts
  • packages/shared/src/consts/providers/credentials/provider-credentials.ts
  • libs/internal-sdk/src/models/components/providersidenum.ts
  • packages/shared/src/consts/providers/channels/chat.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use functional and declarative programming patterns; avoid classes

Files:

  • libs/dal/src/repositories/integration/integration.schema.ts
  • packages/providers/src/lib/chat/index.ts
  • packages/providers/src/lib/chat/maqsam-whatsapp/types/maqsam-whatsapp.types.ts
  • packages/shared/src/entities/integration/credential.interface.ts
  • packages/framework/src/schemas/providers/chat/index.ts
  • packages/shared/src/types/providers.ts
  • packages/framework/src/shared.ts
  • packages/shared/src/consts/providers/credentials/secure-credentials.ts
  • packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts
  • libs/application-generic/src/factories/chat/handlers/maqsam.handler.ts
  • libs/application-generic/src/factories/chat/chat.factory.ts
  • apps/api/src/app/integrations/dtos/credentials.dto.ts
  • apps/worker/src/app/workflow/usecases/send-message/send-message-chat.usecase.ts
  • packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.spec.ts
  • packages/shared/src/consts/providers/credentials/provider-credentials.ts
  • libs/internal-sdk/src/models/components/providersidenum.ts
  • packages/shared/src/consts/providers/channels/chat.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Favor named exports for components

Files:

  • libs/dal/src/repositories/integration/integration.schema.ts
  • packages/providers/src/lib/chat/index.ts
  • packages/providers/src/lib/chat/maqsam-whatsapp/types/maqsam-whatsapp.types.ts
  • packages/shared/src/entities/integration/credential.interface.ts
  • packages/framework/src/schemas/providers/chat/index.ts
  • packages/shared/src/types/providers.ts
  • packages/framework/src/shared.ts
  • packages/shared/src/consts/providers/credentials/secure-credentials.ts
  • packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts
  • libs/application-generic/src/factories/chat/handlers/maqsam.handler.ts
  • libs/application-generic/src/factories/chat/chat.factory.ts
  • apps/api/src/app/integrations/dtos/credentials.dto.ts
  • apps/worker/src/app/workflow/usecases/send-message/send-message-chat.usecase.ts
  • packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.spec.ts
  • packages/shared/src/consts/providers/credentials/provider-credentials.ts
  • libs/internal-sdk/src/models/components/providersidenum.ts
  • packages/shared/src/consts/providers/channels/chat.ts
apps/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer interfaces over types in backend code

Files:

  • apps/api/src/app/integrations/dtos/credentials.dto.ts
🧬 Code graph analysis (5)
packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts (3)
packages/stateless/src/lib/provider/provider.interface.ts (3)
  • IChatProvider (203-210)
  • IChatOptions (94-104)
  • ISendMessageSuccessResponse (114-118)
packages/stateless/src/lib/provider/channel-data.type.ts (1)
  • PhoneData (49-53)
packages/providers/src/lib/chat/maqsam-whatsapp/types/maqsam-whatsapp.types.ts (2)
  • ISendMessageRes (1-5)
  • ISendMessageFailureRes (7-10)
libs/application-generic/src/factories/chat/chat.factory.ts (1)
libs/application-generic/src/factories/chat/handlers/maqsam.handler.ts (1)
  • MaqsamWhatsAppHandler (5-15)
apps/worker/src/app/workflow/usecases/send-message/send-message-chat.usecase.ts (1)
packages/js/src/session/session.ts (1)
  • subscriber (41-43)
packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.spec.ts (2)
packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts (1)
  • MaqsamWhatsAppProvider (13-64)
packages/stateless/src/lib/provider/provider.interface.ts (1)
  • IChatOptions (94-104)
packages/shared/src/consts/providers/credentials/provider-credentials.ts (1)
packages/shared/src/consts/providers/provider.interface.ts (1)
  • IConfigCredential (59-84)
🔇 Additional comments (14)
packages/providers/src/lib/chat/index.ts (1)

5-5: LGTM!

The export follows the existing pattern and is correctly placed in alphabetical order.

packages/framework/src/shared.ts (1)

178-178: LGTM!

The new enum member follows the existing naming conventions and patterns.

packages/shared/src/entities/integration/credential.interface.ts (1)

56-56: LGTM!

The new optional accessSecret field follows the existing pattern for credential fields.

packages/shared/src/consts/providers/credentials/provider-credentials.ts (1)

929-942: LGTM!

The credential configuration follows the established pattern and correctly defines the required AccessKey and AccessSecret fields for the Maqsam WhatsApp provider.

libs/dal/src/repositories/integration/integration.schema.ts (1)

73-73: LGTM!

The schema addition correctly mirrors the ICredentials interface change and follows the existing pattern.

packages/framework/src/schemas/providers/chat/index.ts (1)

18-18: LGTM!

The schema registration follows the existing pattern and correctly uses genericProviderSchemas.

packages/shared/src/types/providers.ts (2)

55-55: LGTM!

The new AccessSecret enum member follows the existing naming conventions.


139-139: LGTM!

The new MaqsamWhatsApp enum member follows the existing naming conventions and patterns.

apps/worker/src/app/workflow/usecases/send-message/send-message-chat.usecase.ts (1)

300-316: Verify integrationId handling for Maqsam WhatsApp to match WhatsApp Business.

The new Maqsam WhatsApp provider is added alongside WhatsApp Business at lines 300-316 with identical structure and the same @ts-expect-error comments about missing _integrationId. Both are phone-based WhatsApp providers.

However, at lines 386-389, the integrationId logic applies a workaround only to WhatsAppBusiness:

const integrationId =
  subscriberChannel.providerId !== ChatProviderIdEnum.WhatsAppBusiness
    ? subscriberChannel._integrationId
    : undefined;

This means WhatsAppBusiness gets integrationId = undefined (per the SMS-like workaround noted in the comment), while MaqsamWhatsApp receives integrationId = subscriber._integrationId. Since both providers are structurally identical and both require the workaround (as evidenced by matching error suppression comments), confirm whether MaqsamWhatsApp should also be excluded from using _integrationId by including it in the condition.

apps/api/src/app/integrations/dtos/credentials.dto.ts (1)

240-243: LGTM!

The accessSecret field follows the established pattern for optional credential fields and is properly decorated with the necessary validators.

packages/shared/src/consts/providers/credentials/secure-credentials.ts (1)

10-10: LGTM!

Correctly adds AccessSecret to the secure credentials list, ensuring it receives appropriate security treatment alongside other sensitive credentials.

libs/application-generic/src/factories/chat/chat.factory.ts (1)

6-6: LGTM!

The MaqsamWhatsAppHandler is correctly imported and registered in the handlers array, following the established pattern for adding new chat providers.

Also applies to: 31-31

libs/internal-sdk/src/models/components/providersidenum.ts (1)

90-90: LGTM!

The MaqsamWhatsApp enum value follows the established kebab-case naming convention and is correctly positioned in the providers list.

packages/shared/src/consts/providers/channels/chat.ts (1)

7-7: LGTM!

The Maqsam WhatsApp provider configuration is complete and follows the established pattern. The betaVersion: true flag appropriately marks this as a new provider, and the documentation reference is correctly set.

Also applies to: 113-121

Comment on lines +10 to +14
buildProvider(credentials: ICredentials) {
const config: { accessKey: string, accessSecret: string } = { accessKey: credentials.accessKey, accessSecret: credentials.accessSecret };

this.provider = new MaqsamWhatsAppProvider(config);
}
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

Add credential validation to prevent runtime errors.

The buildProvider method doesn't validate that credentials.accessKey and credentials.accessSecret are defined before passing them to the provider. If either credential is missing, the provider would create an invalid Basic auth token like "dW5kZWZpbmVkOnVuZGVmaW5lZA==" (base64 of "undefined:undefined"), causing API authentication failures.

🔎 Recommended validation
 buildProvider(credentials: ICredentials) {
+  if (!credentials.accessKey || !credentials.accessSecret) {
+    throw new Error('Maqsam WhatsApp provider requires both accessKey and accessSecret');
+  }
+
   const config: { accessKey: string, accessSecret: string } = { accessKey: credentials.accessKey, accessSecret: credentials.accessSecret };
 
   this.provider = new MaqsamWhatsAppProvider(config);
 }

Alternatively, use non-null assertions if validation happens upstream:

-  const config: { accessKey: string, accessSecret: string } = { accessKey: credentials.accessKey, accessSecret: credentials.accessSecret };
+  const config = { 
+    accessKey: credentials.accessKey!, 
+    accessSecret: credentials.accessSecret! 
+  };
🤖 Prompt for AI Agents
In libs/application-generic/src/factories/chat/handlers/maqsam.handler.ts around
lines 10 to 14, the buildProvider method currently constructs a config using
credentials.accessKey and credentials.accessSecret without validating them; add
explicit checks that both accessKey and accessSecret are non-empty strings
(e.g., typeof === 'string' && length > 0) and throw a clear Error or return
early with a logged error if validation fails, so you never pass undefined to
MaqsamWhatsAppProvider; after validation, construct the config and instantiate
the provider as before.

Comment on lines +28 to +38
const options: IChatOptions = {
phoneNumber: '+111111111',
content: 'Template message',
customData: {
templateId: '123',
templateVariables: {
name: 'John',
company: 'Acme',
},
},
};
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 | 🔴 Critical

Critical: Test uses deprecated phoneNumber field but provider expects channelData.

Lines 29 and 44 use the deprecated phoneNumber field, but according to the provider implementation (from the relevant code snippet), line 35 extracts the phone number from (options.channelData as PhoneData).endpoint.phoneNumber. This mismatch means the tests don't accurately validate the provider's actual behavior and would likely fail at runtime if the provider is called with these test options.

🔎 Corrected test structure
Also applies to: 40-47

<details>
<summary>🤖 Prompt for AI Agents</summary>

In
packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.spec.ts
around lines 28 to 38 (and also apply same change to lines ~40-47), the test
builds options using the deprecated phoneNumber field but the provider reads the
recipient from options.channelData.endpoint.phoneNumber; update the test options
to include channelData: { endpoint: { phoneNumber: '' } } (move the
existing '+111111111' into that structure) and remove or stop using the
top-level phoneNumber field so the spec mirrors the provider’s expected input
shape and validates actual behavior.


</details>

<!-- fingerprinting:phantom:poseidon:puma -->

<!-- This is an auto-generated comment by CodeRabbit -->

Comment on lines +30 to +32
headers: {
Authorization: `Basic ${token}`,
'Content-Type': 'application/x-www-form-urlencoded',
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the maqsam-whatsapp provider file
cat -n packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts

Repository: novuhq/novu

Length of output: 2572


🏁 Script executed:

# Find the BaseProvider class and its transform method
fd -t f "base.provider" | head -20

Repository: novuhq/novu

Length of output: 148


🏁 Script executed:

# Examine the BaseProvider file to find the transform method
cat -n packages/providers/src/base.provider.ts

Repository: novuhq/novu

Length of output: 5436


🏁 Script executed:

# Search for URL encoding logic and axios transformers in the maqsam whatsapp provider
rg -n "URLSearchParams|urlencoded|qs\.|stringify" packages/providers/src/lib/chat/maqsam-whatsapp/ -A 3 -B 3

# Also check if there are test files
find packages/providers -path "*maqsam*" -type f | grep -i test

Repository: novuhq/novu

Length of output: 3691


🏁 Script executed:

# Examine the test file to understand the expected behavior
cat -n packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.spec.ts

Repository: novuhq/novu

Length of output: 4728


🏁 Script executed:

# Find the axiosSpy utility
find packages/providers -path "*test*" -name "*spy*axios*" -type f

Repository: novuhq/novu

Length of output: 101


🏁 Script executed:

# Examine the axiosSpy utility
cat -n packages/providers/src/utils/test/spy-axios.ts

Repository: novuhq/novu

Length of output: 1380


🏁 Script executed:

# Verify if axios has any custom transformers or if there's URL encoding elsewhere
# Check the axios client creation in maqsam-whatsapp.provider.ts more carefully
grep -A 10 "Axios.create" packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts

# Check if URLSearchParams or qs is installed
find packages/providers -name "package.json" | xargs grep -l "qs\|query-string"

Repository: novuhq/novu

Length of output: 433


🏁 Script executed:

# Check dependencies in providers package.json
cat packages/providers/package.json | grep -A 50 "dependencies"

Repository: novuhq/novu

Length of output: 1589


🏁 Script executed:

# Verify that qs is not imported or used in maqsam-whatsapp files
rg -n "import.*qs|from.*qs" packages/providers/src/lib/chat/maqsam-whatsapp/

Repository: novuhq/novu

Length of output: 374


🏁 Script executed:

# Final verification: Check actual axios default behavior documentation 
# by looking at how other providers in the codebase handle form-urlencoded
rg -n "application/x-www-form-urlencoded" packages/providers/src/lib/ -B 5 -A 5

Repository: novuhq/novu

Length of output: 7106


Serialize payload to form-urlencoded format before sending.

The Content-Type header is set to 'application/x-www-form-urlencoded' (line 32), but the code posts payload.body directly as a JavaScript object (line 52). Axios will serialize the object as JSON by default, violating the declared Content-Type and causing the API request to fail.

Use qs.stringify(payload.body) or URLSearchParams to serialize the payload into the key=value&key=value format required by this Content-Type. Other providers in the codebase (unifonic, isendpro-sms) follow this pattern correctly.

🤖 Prompt for AI Agents
In packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts
around lines 30 to 32, the request sets Content-Type:
'application/x-www-form-urlencoded' but sends payload.body as a plain JS object;
update the sending logic to serialize the body into form-urlencoded format
(e.g., use qs.stringify(payload.body) or new
URLSearchParams(payload.body).toString()) before passing it to axios so the
payload matches the header; add/import qs if using it and ensure axios receives
the serialized string while keeping the existing headers.

Comment on lines +37 to +43
private defineMessagePayload(options: IChatOptions) {
return {
RecipientPhone: (options.channelData as PhoneData).endpoint.phoneNumber,
TemplateId: options.customData?.templateId,
TemplateVariables: options.customData?.templateVariables,
}
}
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 | 🔴 Critical

Add validation for channelData and required customData properties.

Line 39 performs an unsafe type cast without validating that channelData exists or is actually of type PhoneData. If channelData is undefined or of a different type, this will cause a runtime error.

Lines 40-41 use optional chaining for customData, but don't validate that templateId is present. Since these appear to be required by the Maqsam API (based on the API structure), sending undefined values will likely cause API failures.

🔎 Proposed fix with validation
 private defineMessagePayload(options: IChatOptions) {
+  if (!options.channelData || options.channelData.type !== 'phone') {
+    throw new Error('Phone number is required in channelData for Maqsam WhatsApp');
+  }
+  
+  if (!options.customData?.templateId) {
+    throw new Error('templateId is required in customData for Maqsam WhatsApp');
+  }
+
+  const phoneData = options.channelData as PhoneData;
+
   return {
-    RecipientPhone: (options.channelData as PhoneData).endpoint.phoneNumber,
+    RecipientPhone: phoneData.endpoint.phoneNumber,
     TemplateId: options.customData?.templateId,
     TemplateVariables: options.customData?.templateVariables,
   }
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts
around lines 37 to 43, the method defineMessagePayload unsafely casts
channelData to PhoneData and allows missing customData fields; add runtime
validation: check that options.channelData exists and has an
endpoint.phoneNumber (use a small type-guard or explicit checks) and throw or
return a descriptive error if missing; verify options.customData and that
customData.templateId (and templateVariables if required) are present and
throw/return a clear error when absent; after validation, construct and return
the payload using the validated values (avoid direct casting and optional
chaining for required API fields).

Comment on lines +1 to +5
export interface ISendMessageRes {
conversationId: string;
messageStatus: string;
result: string;
}
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -type f -path "*maqsam*whatsapp*" -name "*.ts" -o -path "*maqsam*whatsapp*" -name "*.json" | head -20

Repository: novuhq/novu

Length of output: 296


🏁 Script executed:

# Get the full path to the types file mentioned in the review
find . -type f -path "*maqsam-whatsapp.types.ts"

Repository: novuhq/novu

Length of output: 135


🏁 Script executed:

# Search for provider file
find . -type f -path "*maqsam-whatsapp.provider.ts"

Repository: novuhq/novu

Length of output: 132


🏁 Script executed:

# Search for test files related to maqsam
find . -type f -path "*maqsam*" -name "*.spec.ts" -o -path "*maqsam*" -name "*.test.ts"

Repository: novuhq/novu

Length of output: 201


🏁 Script executed:

cat -n ./packages/providers/src/lib/chat/maqsam-whatsapp/types/maqsam-whatsapp.types.ts

Repository: novuhq/novu

Length of output: 309


🏁 Script executed:

cat -n ./packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts

Repository: novuhq/novu

Length of output: 2572


🏁 Script executed:

cat -n ./packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.spec.ts

Repository: novuhq/novu

Length of output: 4728


🏁 Script executed:

find . -type f -name "base.provider.ts" | head -5

Repository: novuhq/novu

Length of output: 96


🏁 Script executed:

# Also search for how CasingEnum.CAMEL_CASE is used
rg "CasingEnum" -A 5 -B 5 --type ts | head -100

Repository: novuhq/novu

Length of output: 9418


🏁 Script executed:

cat -n ./packages/providers/src/base.provider.ts | head -150

Repository: novuhq/novu

Length of output: 5436


🏁 Script executed:

# Search for the transform method in base provider
rg "transform\(" -A 20 ./packages/providers/src/base.provider.ts | head -80

Repository: novuhq/novu

Length of output: 37


🏁 Script executed:

# Check if there's any transformation happening in axiosSpy or if tests are mocking correctly
rg "axiosSpy" -B 10 -A 10 ./packages/providers/src/utils/test/spy-axios.ts 2>/dev/null | head -100

Repository: novuhq/novu

Length of output: 595


🏁 Script executed:

# Verify the actual flow - does the response data get transformed anywhere?
rg "casingTransform\|transform(" ./packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts

Repository: novuhq/novu

Length of output: 159


🏁 Script executed:

# Check if there's any middleware or interceptor that might transform response data
rg "interceptor\|response" ./packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts

Repository: novuhq/novu

Length of output: 37


🏁 Script executed:

# Check what the response flow is - is data transformed after axios call?
rg "casingTransform" ./packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts

Repository: novuhq/novu

Length of output: 37


🏁 Script executed:

# Look at how axios response is handled - does it go through any transformation?
rg "axiosClient.post" -A 5 ./packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts

Repository: novuhq/novu

Length of output: 262


🏁 Script executed:

# Let me check the actual transformation flow more carefully
# Look at line 49 - where transform is called
sed -n '45,65p' ./packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts

Repository: novuhq/novu

Length of output: 687


🏁 Script executed:

# Let me verify my understanding - check if axiosSpy does any transformation or just returns data as-is
cat -n ./packages/providers/src/utils/test/spy-axios.ts

Repository: novuhq/novu

Length of output: 1380


🏁 Script executed:

# Verify the critical path - does the test actually pass?
# Let me check if there are any recent test results or CI logs
# First, let me see what the test framework is and if we can understand test behavior

# Let's trace through the logic one more time:
# 1. Mock returns: { message_id, message_status }
# 2. Provider checks: 'conversationId' in data
# 3. This check should fail

# Let me verify by looking at another provider to see how they handle responses
find . -type f -path "*provider.spec.ts" -type f | head -3 | xargs -I {} bash -c 'echo "=== {} ===" && cat -n {} | head -80'

Repository: novuhq/novu

Length of output: 7371


🏁 Script executed:

# Let me check if maybe the transform is being applied to responses too
# by searching for any response transformation in base provider
rg "response\|data" ./packages/providers/src/base.provider.ts | grep -i "transform\|case"

Repository: novuhq/novu

Length of output: 37


🏁 Script executed:

# Critical check - does the response transformation happen somewhere I missed?
# Let me trace the exact flow: what does casingTransform do to the RESPONSE?

# First, let me check if there are any response interceptors in axios config
grep -r "interceptor" ./packages/providers/src/lib/chat/maqsam-whatsapp/

Repository: novuhq/novu

Length of output: 37


🏁 Script executed:

# Let me also verify if there's any middleware transforming responses
# by searching for any response transformation in similar providers
rg "const { data }" ./packages/providers/src/lib/chat/ -A 3 | head -60

Repository: novuhq/novu

Length of output: 159


🏁 Script executed:

# Most importantly - let me trace the actual test execution path
# The test mocks the response to have message_id, but provider checks for conversationId
# This should fail, but the test expects it to pass. Let me verify this is indeed the case:

sed -n '12,19p' ./packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.spec.ts

Repository: novuhq/novu

Length of output: 203


🏁 Script executed:

# Show the test assertion that expects this to succeed
sed -n '58,58p' ./packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.spec.ts

Repository: novuhq/novu

Length of output: 88


🏁 Script executed:

# Let me verify one more critical thing - is the test actually passing or failing?
# Let me check if there are any CI test results or if I can understand what should happen

# The key question: when mock returns { message_id }, does provider.sendMessage() succeed?
# Based on the code, it should FAIL because:
# 1. Mock: { message_id: 'xyz' }
# 2. Provider checks: 'conversationId' in data → FALSE
# 3. Provider throws error

# So test line 58 (expect(res.id).toBe(messageId)) should FAIL

# Let me verify by checking if there's any response transformation I missed
# by looking at the complete flow one more time in the provider

cat -n ./packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts | sed -n '45,63p'

Repository: novuhq/novu

Length of output: 818


🏁 Script executed:

# Also check - does BaseProvider have any response transformer?
grep -n "response\|Response" ./packages/providers/src/base.provider.ts

Repository: novuhq/novu

Length of output: 37


🏁 Script executed:

# Final verification - check if axios.post response is transformed somewhere
# by searching for any response interceptors or transformers in the entire provider
rg "transformResponse\|responseTransformer" ./packages/providers/src/

Repository: novuhq/novu

Length of output: 37


Critical: Response field mismatch causes provider failure.

The ISendMessageRes interface defines conversationId, but the test mocks show the Maqsam API returns message_id in snake_case. The provider's success check (if ('conversationId' in data && data.conversationId)) will always fail, throwing an error for all API responses.

The interface must be updated to match the actual API response:

 export interface ISendMessageRes {
-  conversationId: string;
+  messageId: string;
   messageStatus: string;
   result: string;
 }

And the provider check accordingly (in maqsam-whatsapp.provider.ts, line 55):

-if ('conversationId' in data && data.conversationId) {
+if ('messageId' in data && data.messageId) {
   return {
-    id: data.conversationId,
+    id: data.messageId,
     date: new Date().toISOString(),
   };
 }
📝 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
export interface ISendMessageRes {
conversationId: string;
messageStatus: string;
result: string;
}
export interface ISendMessageRes {
messageId: string;
messageStatus: string;
result: string;
}
🤖 Prompt for AI Agents
In
packages/providers/src/lib/chat/maqsam-whatsapp/types/maqsam-whatsapp.types.ts
lines 1-5, the interface fields do not match the real Maqsam API response (tests
show snake_case). Replace conversationId with message_id (string) and ensure
other fields reflect the actual response names; then update the provider success
check in
packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts
(around line 55) to test for 'message_id' in the response and use
data.message_id instead of data.conversationId so the provider recognizes
successful API responses.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant