In this article I will show you how to use the Sonera CStream Messaging Web Service API to send an SMS using Python, and a library called SUDS. The CStream API is two-way service for both sending and receiving messages. You obviously need to pay for the service to get access. After you have your credentials, you can start using the service.
The SUDS is a lightweight SOAP Python client for exploring and using web services. A recent version can be installed on Debian based distros with “sudo apt-get install python-suds”, or on almost anything with “pip install suds”.
The one thing good to know before starting out, is that the Sonera web service uses WS Security for authentication, which is found in the suds.wsse module.
Let’s start hacking in iPython:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
kortsi@localhost:~$ ipython Python 2.7.3 (default, Sep 26 2012, 21:51:14) Type "copyright", "credits" or "license" for more information. IPython 0.13.1.rc2 -- An enhanced Interactive Python. ? -> Introduction and overview of IPython's features. %quickref -> Quick reference. -> Python's own help system. object -> Details about 'object', use 'object??' for extra details. In [1]: import logging In [2]: logging.basicConfig(level=logging.DEBUG) In [3]: logging.getLogger('suds.client').setLevel(logging.DEBUG) In [4]: from suds.client import Client In [5]: client = Client('https://saarni.front.sonera.fi/ws/messaging-v2.wsdl') |
At this point you should see a lot of debug messages on the screen. Thats the SUDS querying the WSDL schema and finding out what’s possible. You can take a look at it if it you are interested, but there is a better way to explore the metadata.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
In [6]: print client Suds ( https://fedorahosted.org/suds/ ) version: 0.4.1 (beta) build: R703-20101015 Service ( messaging-v2Service ) tns="http://www.lekab.com/schema/messaging-v2/messages" Prefixes (1) ns0 = "http://www.lekab.com/schema/messaging-v2/messages" Ports (1): (messaging-v2Soap11) Methods (3): GetIncomingMessages(MessageIds messageIds, Attributes attributes, ) GetMessageStatus(MessageIds messageIds, Attributes attributes, ) Send(xs:string sender, Recipients recipients, xs:boolean replyable, xs:string conversationId, Priority priority, xs:dateTime scheduledDelivery, xs:dateTime validTo, xs:string tariff, xs:string contentCategory, xs:string contentMetaData, xs:double vat, xs:string referenceId, Data data, Attributes attributes, ) Types (21): Attachment Attachments Attribute Attributes Data ErrorDetail ErrorDetails IncomingMessage IncomingMessages MessageIds MessageStatus MessageStatuses MmsData MmsPayload OutgoingMessage Payload Priority Recipients SmsData SmsPayload Value |
The list of available methods and and data types is what we are interested in.
The three methods there match the API specification. The names are pretty much self-explanatory:
- Send() sends text messages
- GetMessageStatus() queries the status of sent messages
- GetIncomingMessages() retrieves incoming messages
To invoke those methods, we must be able to create data in specified types. To send an SMS, we need to create an SmsData object, wrapped in a Data object. We can have SUDS create them for us:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
In [7]: data = client.factory.create('Data') In [8]: smsdata = client.factory.create('SmsData') In [9]: data.sms = smsdata In [10]: print data (Data){ sms = (SmsData){ payload = (SmsPayload){ message = None udh = None } encoding = None replaceIfPresent = None flash = None port = None attributes = (Attributes){ attribute[] = <empty> } } } |
That shows what the Data should look like. Let’s also create an SmsPayload object:
1 2 3 4 5 6 7 |
In [11]: payload = client.factory.create('SmsPayload') In [12]: print payload (SmsPayload){ message = None udh = None } |
According to the specification, the message attribute must be a “base64 encoded UTF-8 encoded string”. Let’s test with a string that requires more than plain ASCII:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
In [13]: testmessage = u'Tämä on testi! ("This is a test!" in Finnish)' In [14]: import base64 In [15]: payload.message = base64.encodestring(testmessage.encode('utf8')).strip() In [16]: print payload (SmsPayload){ message = "VMOkbcOkIG9uIHRlc3RpISAoIlRoaXMgaXMgYSB0ZXN0ISIgaW4gRmlubmlzaCk=" udh = None } In [17]: smsdata.payload = payload In [18]: print data (Data){ sms = (SmsData){ payload = (SmsPayload){ message = "VMOkbcOkIG9uIHRlc3RpISAoIlRoaXMgaXMgYSB0ZXN0ISIgaW4gRmlubmlzaCk=" udh = None } encoding = None replaceIfPresent = None flash = None port = None attributes = (Attributes){ attribute[] = <empty> } } } |
Now we have a payload set. The Data object is now ready for sending. We still need to create a list of recipients:
1 2 3 4 5 6 7 8 9 |
In [19]: recipients = client.factory.create('Recipients') In [20]: recipients.recipient.append('358001234567') In [21]: print recipients (Recipients){ recipient[] = "358001234567", } |
The recipient number must be a string, and it must have a country code. A sender id or number is also required, but it can be whatever you choose. We can give it directly to the Send() method when we send the message. But before that, we must set up authentication:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
In [22]: from suds.wsse import Security, UsernameToken, Timestamp In [23]: security = Security() In [24]: username = UsernameToken('username', 'secretpassword') In [25]: security.tokens.append(username) In [26]: timestamp = Timestamp(validity=120) In [27]: security.tokens.append(timestamp) In [28]: client.set_options(wsse=security) |
We set up a username/password token, and a timestamp. Now we have 120 seconds of time to send a message using the security tokens.
At this point, we should have everything we need to send our message. Use the Send() method:
1 |
In [29]: client.service.Send(sender='SENDERID', recipients=recipients, data=data) |
You will get a reply from the remote server. It will either tell you that it failed for some reason, but if everything went ok, the message was queued for delivery, and the response object will look something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
Out[29]: (reply){ messageStatus[] = (MessageStatus){ statusCode = 0 statusText = "QUEUED" id = "1-9876543" sender = "SENDERID" recipient = "358001234567" time = 2013-03-11 15:52:37.000984 billingStatus = 0 attributes = (Attributes){ attribute[] = (Attribute){ name = "NumberOfMessages" value = (Value){ integer = 1 } }, (Attribute){ name = "NumberOfCharacters" value = (Value){ integer = 45 } }, } }, attributes = (Attributes){ attribute[] = (Attribute){ name = "TotalNumberOfMessages" value = (Value){ integer = 1 } }, } } |
The recipient should receive the message shortly.
To wrap it up, let’s make a simple class out of it. Put this to a file called cstream.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
WSDL_URL = 'https://saarni.front.sonera.fi/ws/messaging-v2.wsdl' from base64 import encodestring as b64enc from suds.client import Client from suds.wsse import Security, UsernameToken, Timestamp class SimpleSmsSender: """Sends an SMS using CStream Web Service API Give your credentials as arguments to the constructor. Example: sender = SimpleSmsSender('username', 'secretpassword') """ def __init__(self, username, password): self.username = username self.password = password self.client = Client(WSDL_URL) def send(self, sender, recipient, message): """Give sender id, recipient number and message as strings. The message can be a unicode string. Example: sender.send('SENDERID', '358001234567', u'Hello world!') """ f = self.client.factory data = f.create('Data') smsdata = f.create('SmsData') data.sms = smsdata payload = f.create('SmsPayload') payload.message = b64enc(message.encode('utf8')).strip() smsdata.payload = payload recipients = f.create('Recipients') recipients.recipient.append(recipient) security = Security() username = UsernameToken(self.username, self.password) security.tokens.append(username) timestamp = Timestamp() security.tokens.append(timestamp) self.client.set_options(wsse=security) return self.client.service.Send(sender=sender, recipients=recipients, data=data) |
To use:
1 2 3 4 5 |
In [1]: from cstream import SimpleSmsSender In [2]: sender = SimpleSmsSender('username', 'secretpassword') In [3]: sender.send('FROM ME', '358001234567', u'Hello world') |
That’s it! The receiving of messages, the checking of message delivery status, and the handling of exceptions are left as exercises to the reader (and to myself).