Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/notification-twilio #9648

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/types/src/notification/providers/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./logger"
export * from "./sendgrid"
export * from "./twilio"
6 changes: 6 additions & 0 deletions packages/core/types/src/notification/providers/twilio.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface TwilioNotificationServiceOptions {
account_sid: string
auth_token: string
from?: string
messaging_service_sid?: string
}
6 changes: 6 additions & 0 deletions packages/medusa/src/modules/notification-twilio.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import TwilioNotificationProvider from "@medusajs/notification-twilio"

export * from "@medusajs/notification-twilio"

export default TwilioNotificationProvider
export const discoveryPath = require.resolve("@medusajs/notification-twilio")
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export class SendgridNotificationService extends AbstractNotificationProviderSer
} else {
// we can't mix html and templates for sendgrid
mailContent = {
templateId: notification.template,
templateId: notification.template || "",
}
}

Expand Down
Empty file.
10 changes: 10 additions & 0 deletions packages/modules/providers/notification-twilio/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const defineJestConfig = require("../../../../define_jest_config")
module.exports = defineJestConfig({
moduleNameMapper: {
"^@models": "<rootDir>/src/models",
"^@services": "<rootDir>/src/services",
"^@repositories": "<rootDir>/src/repositories",
"^@types": "<rootDir>/src/types",
"^@utils": "<rootDir>/src/utils",
},
})
46 changes: 46 additions & 0 deletions packages/modules/providers/notification-twilio/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"name": "@medusajs/notification-twilio",
"version": "0.0.1",
"description": "Twilio notification provider for Medusa",
"main": "dist/index.js",
"repository": {
"type": "git",
"url": "https://github.com/medusajs/medusa",
"directory": "packages/modules/providers/notification-twilio"
},
"files": [
"dist",
"!dist/**/__tests__",
"!dist/**/__mocks__",
"!dist/**/__fixtures__"
],
"engines": {
"node": ">=20"
},
"author": "Medusa",
"license": "MIT",
"scripts": {
"test": "jest --passWithNoTests src",
"test:integration": "jest --forceExit -- integration-tests/**/__tests__/**/*.spec.ts",
"build": "rimraf dist && tsc --build ./tsconfig.json",
"watch": "tsc --watch"
},
"devDependencies": {
"@medusajs/framework": "^0.0.1",
"@swc/core": "^1.7.28",
"@swc/jest": "^0.2.36",
"jest": "^29.7.0",
"rimraf": "^5.0.1",
"typescript": "^5.6.2"
},
"dependencies": {
"twilio": "^5.3.3"
},
"peerDependencies": {
"@medusajs/framework": "^0.0.1"
},
"keywords": [
"medusa-provider",
"medusa-provider-twilio"
]
}
8 changes: 8 additions & 0 deletions packages/modules/providers/notification-twilio/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ModuleProvider, Modules } from "@medusajs/framework/utils"
import { TwilioNotificationService } from "./services/twilio"

const services = [TwilioNotificationService]

export default ModuleProvider(Modules.NOTIFICATION, {
services,
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import {
Logger,
NotificationTypes,
TwilioNotificationServiceOptions,
} from "@medusajs/framework/types"
import {
AbstractNotificationProviderService,
MedusaError,
} from "@medusajs/framework/utils"
import Twilio from "twilio"

type InjectedDependencies = {
logger: Logger
}

type SmsContent = Required<
Omit<NotificationTypes.NotificationContent, "subject" | "html">
>

interface TwilioServiceConfig {
accountSid: string
authToken: string
from?: string
messagingServiceSid?: string
}

export class TwilioNotificationService extends AbstractNotificationProviderService {
static identifier = "notification-twilio"
protected config_: TwilioServiceConfig
protected logger_: Logger
protected client_: Twilio.Twilio

constructor(
{ logger }: InjectedDependencies,
options: TwilioNotificationServiceOptions
) {
super()

this.config_ = {
accountSid: options.account_sid,
authToken: options.auth_token,
from: options.from,
messagingServiceSid: options.messaging_service_sid,
}
this.logger_ = logger
this.client_ = Twilio(this.config_.accountSid, this.config_.authToken)
}
/**
* Sends a notification via Twilio SMS (or MMS if mediaUrls are provided)
* @param notification the notification data
* @returns a promise that resolves when the message is sent
*/

async send(
notification: NotificationTypes.ProviderSendNotificationDTO
): Promise<NotificationTypes.ProviderSendNotificationResultsDTO> {
if (!notification) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"No notification information provided"
)
}
const content = notification.content as SmsContent

if (!content?.text) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Message body (content.text) is required for SMS`
)
}

const mediaUrls = notification.data?.mediaUrls as string[] | undefined
const fromNumber = notification.from?.trim() || this.config_.from
const messagingService =
(notification.data?.messagingServiceSid as string) ||
this.config_.messagingServiceSid

if (!notification.to) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Recipient to (#) is required"
)
}

if (!fromNumber && !messagingService) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Either 'from' number or 'messagingServiceSid' must be provided`
)
}
const smsData = {
to: notification.to,
body: content.text,
from: fromNumber || undefined,
messagingServiceSid: messagingService || undefined,
mediaUrl: mediaUrls && mediaUrls.length > 0 ? mediaUrls : undefined,
}

try {
const message = await this.client_.messages.create(smsData)
const messageBody =
typeof message.body === "string"
? JSON.parse(message.body)
: message.body
if (messageBody.error_code) {
throw new MedusaError(
MedusaError.Types.UNEXPECTED_STATE,
`Failed to send SMS: ${messageBody.error_code} - ${messageBody.error_message}`
)
}

return { id: message.sid }
} catch (error: any) {
const errorCode = error.code
const responseError = error.response?.body?.errors?.[0]
throw new MedusaError(
MedusaError.Types.UNEXPECTED_STATE,
`Failed to send SMS: ${errorCode} - ${
responseError?.message ?? "unknown error"
}`
)
}
}
}
12 changes: 12 additions & 0 deletions packages/modules/providers/notification-twilio/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "../../../../_tsconfig.base.json",
"compilerOptions": {
"paths": {
"@models": ["./src/models"],
"@services": ["./src/services"],
"@repositories": ["./src/repositories"],
"@types": ["./src/types"],
"@utils": ["./src/utils"]
}
}
}
Loading