PitzKey… Can you give us more (preferably documented) information on that process?
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.
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?
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,
- 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
- Install all packages required by the script
- Run the application in the background, you can create a service or start it with pm2
- Open port 8888 in your direwall, you can limit it to skyetel SMS IP (52.14.37.123)
- Get a SID and Secret from your skyetel account, used to authenticate when sending outbound messages.
- 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…
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!
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.
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?
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)})
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?
- You don’t need the :
- You can see it in the asterisk logs /var/log/asterisk/full
This is exciting!
I appreciate your help with this. I’ll update you when I get it working!
What does the setup on the skyetel number end up looking like for the url and post method?
Forward = Callback URL
URL = Your PBX IP Port 8888 (Unless you changed the listening port in the script)
Method = Post
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
Interesting - If I have webrtc open, the message will deliver to it. But if its closed the message won’t deliver to webrtc
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.
Is there a way to forward “sip_messages” to “vitxi_messages”?