SMS/MMS in VitXiWebRTC and Mobile apps

PitzKey… Can you give us more (preferably documented) information on that process?

2 Likes

Hi @fnlauria and @mrizkowsky,

Here’s the idea in a nutshell:

  • Build a webhook that supports receiving SMS from your SMS provider.
  • In the webhook parse and deliver the data to an Asterisk dialplan, either via AMI or callfiles.
  • In the dialplan, send the data to the VitXi endpoint.
  • Modify the outgoing messages context to construct the data properly and call your provider’s CURL command to send the SMS

This is the entire idea in short. I will see if I have time to write proper documentation.

5 Likes

Hello,
Did you find any time to look into this?
I unfortunately do not have any programming skills, but would love to try to implement this.
Will this dump the text function into the chat section on vitxi?
What about sending it to the desk phone?

1 Like

Does your provider offer SMS via SIP Message? if yes it should be pretty simple to implement.

SMS does come in in the chat section in vitxi,

You should be able to send it to the desk phone if the phone supports SMS

Skyetel apparently only supports callback url.
https://support.skyetel.com/hc/en-us/articles/360056299914-SMS-MMS-API

Hello, @mrizkowsky

In that case you’ll need a webhook to accept the SMS, and a dialplan to process the incoming and outgoing SMS
Below you’ll find a node.js express server which you can use to accept and parse the incoming SMS and add it to a call file which will take it to the dialplan for processing,

For this to work you’ll need the below,

  1. Create a temp directory in the same directory the script is in, it’s used to create the call file before being moved to the asterisk spooler
  2. Install all packages required by the script
  3. Run the application in the background, you can create a service or start it with pm2
  4. Open port 8888 in your direwall, you can limit it to skyetel SMS IP (52.14.37.123)
  5. Get a SID and Secret from your skyetel account, used to authenticate when sending outbound messages.
  6. Create a file with the custom dialplans (i.e. /etc/asterisk/vitalpbx/extensions__90-custom.conf)

Webhook for inbound

#!/usr/bin/env node

const fs = require('fs')
const {exec} = require('child_process')
const express = require('express')
const cors = require("cors")
const bodyParse = require('body-parser')
const app = express()
const port = 8888;

app.use(cors())
app.use(bodyParse.json({limit:'1mb',}))

app.post('/', (req, res) => {
  const {from, to, text} =req.body
  if(!from || !to||!text)return res.status(422).send('An unexpected error occured')

  const sanitzisedText = text.replace(/[^\w ]/g,'')

  const fileName = `temp/${new Date().toISOString().match(/^[^\.]+/)[0]}.call`

 fs.writeFileSync(fileName,
`Channel: Local/s@dummy-answer
Callerid: "SMS" <${from}>
Context: skyetel-sms
Extension: s
Priority: 1
Setvar: from=${from}
Setvar: to=${to}
Setvar: body=${sanitzisedText}`)

fs.renameSync(fileName, fileName.replace('temp','/var/spool/asterisk/outgoing/'))


res.send('Success!')
})

app.listen(port, () => {
  try {
    fs.readdirSync('temp')
  } catch (error) {
    fs.mkdirSync('temp')
  }

  console.log(`SMS app listening at http://localhost:${port}`)
})

Inbound Dialplan

[dummy-answer]
exten => s,1,Answer()
 same => n,Wait(5)
 same => n,Hangup()

[skyetel-sms]
exten => s,1,Answer()
 same => n,Set(SMS_FROM=${FILTER(0-9,${from})})
 same => n,Set(SMS_TO=${FILTER(0-9,${to})})
 same => n,Set(MESSAGE(body)=${body})
 same => n,Noop(From: ${SMS_FROM})
 same => n,Noop(TO: ${SMS_TO})
 same => n,Noop(Body: ${MESSAGE(body)})
 same => n,Set(SMS_TO=${SMS_TO:-4}) ;The last 4 digits of our DIDs match the extension number, I couldn't figure out any other way to route to an extension based on the DID so you might have to hard code it ( i.e. Set(SMS_TO=${IF($["${SMS_TO}"="1234567890"]?101:102)}) )
 same => n,Set(TENANT=) ;Enter your tenant path
 same => n,Set(DIAL_STRING=${DB(${TENANT}/extensions/${SMS_TO}/dial)})
 same => n,GotoIf($["${DIAL_STRING}"=""]?sendfailedmsg)
 same => n,Set(COUNTER=1)
 same => n,Set(CURRENT_DEVICE=${CUT(DIAL_STRING,&,${COUNTER})})
 same => n,While($[${EXISTS(${CURRENT_DEVICE})}])
 same => n,Set(TECHNOLOGY=${CUT(CURRENT_DEVICE,/,1)})
 same => n,Set(USER=${CUT(CURRENT_DEVICE,/,2)})
 same => n,GotoIf($[$["${TECHNOLOGY}"="IAX2"]|$["${TECHNOLOGY}"="DAHDI"]]?next)
 same => n,MessageSend(${TOLOWER(${TECHNOLOGY})}:${USER},${SMS_FROM})
 same => n,NoOp(SMS Status to ${CURRENT_DEVICE}: ${MESSAGE_SEND_STATUS})
 same => n(next),Set(COUNTER=$[${COUNTER} + 1])
 same => n,Set(CURRENT_DEVICE=${CUT(DIAL_STRING,&,${COUNTER})})
 same => n,EndWhile()
 same => n,Goto(h,1)
 same => n(sendfailedmsg),Noop(The message to extension ${SMS_TO} has failed. Status: ${MESSAGE_SEND_STATUS})
 same => n(failed),Hangup()

Outbound Dialplan

[messages]
exten => _ZXXXXXX!,1,Noop(SMS Sending) ;Accepts 7 or more digits with the first digit being [1-9]
 same => n,GotoIf($["x${CUT(MESSAGE(to),<,2)}x"="xx"]?noname)
 same => n,Set(SMS_NUM=${CUT(MESSAGE(to),<,2)})
 same => n,Set(SMS_NUM=${CUT(SMS_NUM,>,1)})
 same => n,Set(SMS_TECH=${CUT(SMS_NUM,:,1)})
 same => n,Set(SMS_REAL_TECH=${CUT(MESSAGE(to),:,1)})
 same => n,GotoIf($["${SMS_TECH}"="${SMS_REAL_TECH}"]?noname)
 same => n,Set(MESSAGE(to)=${STRREPLACE(SMS_NUM,${SMS_TECH},${SMS_REAL_TECH})})
 same => n(noname),Set(SMS_TO=${FILTER(0-9,${CUT(MESSAGE(to),@,1)})})
 same => n,GotoIf($[${LEN(${SMS_TO})} < 10]?messages-internal,${EXTEN},1)
 same => n,ExecIf($["${LEN(${SMS_TO})}"="10"]?Set(SMS_TO=1${SMS_TO}))
 same => n,ExecIf($[${LEN(${SMS_TO})} > 11]?Hangup())
 same => n,Set(FROM=${CUT(MESSAGE(from),<,2)})
 same => n,Set(ACTUALFROM=${CUT(FROM,@,1)})
 same => n,ExecIf($["${ACTUALFROM}"=~"_"]?Set(ACTUALFROM=${CUT(ACTUALFROM,_,1)}))
 same => n,Set(SMS_FROM=${FILTER(0-9,${CUT(ACTUALFROM,:,2)})})
 same => n,Set(SMS_CID=1234567${SMS_FROM}) ;Again if the last 4 of your DID matches the extension number. Note: this must be an 11 digit number and it must have SMS enabled in Skyetel
 same => n,Noop(To: ${SMS_TO})
 same => n,Noop(From: ${SMS_FROM})
 same => n,Answer()
 same => n,Set(SendSMS=${SHELL(curl -X POST -v -H "Content-type: application/json" --user <SID>:<SECRET> --data "{ \"to\": \"${SMS_TO}\", \"text\": \"${MESSAGE(body)}\" }" https://sms.skyetel.com/v1/out?from=${SMS_CID})}) ;You'll have to get the SID and SECRET from your Skytel account
 same => n,Hangup()

P.S. I am not to familiar with JavaScript, but I tested this setup with Skyetel and it worked.

Hope that helps…

2 Likes

This looks like amazing work.
So the webhook is the script?
How do I know what packages it needs?
Do I assume it already has what is needed?
I can do some googling to figure out how to follow steps 2 and 3 but if you are feeling generous to provide some more tips to accomplish those I would be appreciative.

Regarding the dialplans pulling the number - is there a way to grab it from the inbound route from the extension configuration?
Also would I append the dialplan stuff to the existing dialplan?
Or is asterisk smart enough to use multiple dial plan files?
Thanks for working on this!

1 Like

Yes

Running this command in the same directory the script is in should install the packages needed npm install fs child_process express cors body-parser

I couldn’t find one unfortunately

All files that in the /etc/asterisk/vitalpbx directory that start with extensions__ and end with .conf are included in the dial plan, so you can create a custom file (i.e. extensions__90-custom.conf) and it will be included.

1 Like

In my use case I will have to hard code it.
I assume I have a line for each number I have to hardcode?
same => n,Set(SMS_TO=${IF($[“${SMS_TO}”=“1234567890”]?101:102)})
is the 101 and 102 the extension?
Does this mean texts to “1234567890” translate to extension 101 and 102?

same => n,Set(SMS_CID=1234567${SMS_FROM}) ;Again if the last 4 of your DID matches the extension number. Note: this must be an 11 digit number and it must have SMS enabled in Skyetel

Hows does the outbound work for mapping extensions manually?

1 Like

Unfortunately yes

This means that if the DID is 1234567890 then it will send it to 101, otherwise it will send it to 202. (An if statement it will execute whatever is after the ? when true and after the : when false)

Set(SMS_CID=${IF($["${SMS_FROM}"="101"]?1234567890)})
1 Like

I think the last 2 questions before I can move foward is do I have to have the : false statement?
or will this work?

same => n,Set(SMS_TO=${IF($[“${SMS_TO}”=“1234567890”]?101)})

Is there a way to keep a record of all texts in case of an issue in the future for ediscovery purposes?

1 Like
  1. You don’t need the :
  2. You can see it in the asterisk logs /var/log/asterisk/full
2 Likes

This is exciting!
I appreciate your help with this. I’ll update you when I get it working!

1 Like

What does the setup on the skyetel number end up looking like for the url and post method?

1 Like

Forward = Callback URL
URL = Your PBX IP Port 8888 (Unless you changed the listening port in the script)
Method = Post

1 Like

I got an inbound message to come in to my hard phone, but it won’t send to the webrtc or the mobile endpoints

[2022-04-04 08:05:14] WARNING[10221] res_pjsip_messaging.c: Dest: '116_Vitxi' MSG SEND FAIL: Found endpoint '116_Vitxi' but didn't find an aor/contact for it
[2022-04-04 08:05:14] ERROR[10221] res_pjsip_messaging.c: PJSIP MESSAGE - Could not find endpoint '116_Vitxi' and no default outbound endpoint configured

Outbound messages are not working.

[2022-04-04 08:08:38] VERBOSE[1559][C-00000005] pbx.c: Executing [1XXXXXXXXXX@messages:1] NoOp("Message/ast_msg_queue", "SMS Sending") in new stack
[2022-04-04 08:08:38] VERBOSE[1559][C-00000005] pbx.c: Executing [1XXXXXXXXXX@messages:2] GotoIf("Message/ast_msg_queue", "1?noname") in new stack
[2022-04-04 08:08:38] VERBOSE[1559][C-00000005] pbx_builtins.c: Goto (messages,1XXXXXXXXXX,9)
[2022-04-04 08:08:38] VERBOSE[1559][C-00000005] pbx.c: Executing [1XXXXXXXXXX@messages:9] Set("Message/ast_msg_queue", "SMS_TO=1XXXXXXXXXX") in new stack
[2022-04-04 08:08:38] VERBOSE[1559][C-00000005] pbx.c: Executing [1XXXXXXXXXX@messages:10] GotoIf("Message/ast_msg_queue", "0?messages-internal,1XXXXXXXXXX,1") in new stack
[2022-04-04 08:08:38] VERBOSE[1559][C-00000005] pbx.c: Executing [1XXXXXXXXXX@messages:11] ExecIf("Message/ast_msg_queue", "0?Set(SMS_TO=11XXXXXXXXXX)") in new stack
[2022-04-04 08:08:38] VERBOSE[1559][C-00000005] pbx.c: Executing [1XXXXXXXXXX@messages:12] ExecIf("Message/ast_msg_queue", "0?Hangup()") in new stack
[2022-04-04 08:08:38] VERBOSE[1559][C-00000005] pbx.c: Executing [1XXXXXXXXXX@messages:13] Set("Message/ast_msg_queue", "FROM=sip:116@XXX>") in new stack
[2022-04-04 08:08:38] VERBOSE[1559][C-00000005] pbx.c: Executing [1XXXXXXXXXX@messages:14] Set("Message/ast_msg_queue", "ACTUALFROM=sip:116") in new stack
[2022-04-04 08:08:38] VERBOSE[1559][C-00000005] pbx.c: Executing [1XXXXXXXXXX@messages:15] ExecIf("Message/ast_msg_queue", "0?Set(ACTUALFROM=sip:116)") in new stack
[2022-04-04 08:08:38] VERBOSE[1559][C-00000005] pbx.c: Executing [1XXXXXXXXXX@messages:16] Set("Message/ast_msg_queue", "SMS_FROM=116") in new stack
[2022-04-04 08:08:38] VERBOSE[1559][C-00000005] pbx.c: Executing [1XXXXXXXXXX@messages:17] Set("Message/ast_msg_queue", "SMS_CID=1XXXXXXXXXX) same => n,Noop(To: 1XXXXXXXXXX") in new stack
[2022-04-04 08:08:38] VERBOSE[1559][C-00000005] pbx.c: Executing [1XXXXXXXXXX@messages:18] NoOp("Message/ast_msg_queue", "From: 116") in new stack
[2022-04-04 08:08:38] VERBOSE[1559][C-00000005] pbx.c: Executing [1XXXXXXXXXX@messages:19] Answer("Message/ast_msg_queue", "") in new stack
[2022-04-04 08:08:38] VERBOSE[1559][C-00000005] pbx.c: Executing [1XXXXXXXXXX@messages:20] Set("Message/ast_msg_queue", "SendSMS=") in new stack
[2022-04-04 08:08:38] VERBOSE[1559][C-00000005] pbx.c: Executing [1XXXXXXXXXX@messages:21] Hangup("Message/ast_msg_queue", "") in new stack
[2022-04-04 08:08:38] VERBOSE[1559][C-00000005] pbx.c: Spawn extension (messages, 1XXXXXXXXXX, 21) exited non-zero on 'Message/ast_msg_queue'

Edit: Adjusted formatting for better read - mod

1 Like

@mrizkowsky Please use preformatted text or pastebin.com when posting logs.

2 Likes

Interesting - If I have webrtc open, the message will deliver to it. But if its closed the message won’t deliver to webrtc

1 Like

Look at the SMS like a phone call. If the device is offline it won’t get the message.

You can technically have a retry in conjunction with the ${DEVICE_STATE} function. But then if you restart the server it’ll get lost as well.

1 Like

Is there a way to forward “sip_messages” to “vitxi_messages”?

1 Like