Package: aiosmpplib
An asynchronous SMPP library for use with asyncio.Inspired bynazlibrary by Komu Wairagu. Initial intention was to add missing functionality
to existing library. But in the end, the code has been almost completely rewritten and released
as a separate library.SMPP is a protocol designed for the transfer of short message data between External Short
Messaging Entities(ESMEs), Routing Entities(REs) and Short Message Service Center(SMSC).
-WikipediaCurrently, only partial ESME functionality is implemented, and only SMPP version 3.4 is supported.Full documentation is not available at this time.InstallationpipinstallaiosmpplibRequirementsPython 3.7+ is required. Currently, aiosmpplib does not have any third-party dependencies,
but it optionally usesorjsonlibrary for JSON serialization and logging.Quick startimportasynciofromaiosmpplibimportESME,PhoneNumber,SubmitSmfromaiosmpplib.logimportDEBUGasyncdefmain():# Create ESME instance.esme=ESME(smsc_host='127.0.0.1',smsc_port=2775,system_id='test',password='test',log_level=DEBUG,)# Queue messages to send.foriinrange(0,5):msg=SubmitSm(short_message=f'Test message{i}',source=PhoneNumber('254722111111'),destination=PhoneNumber('254722999999'),log_id=f'id-{i}',)awaitesme.broker.enqueue(msg)# Start ESME. It will run until stopped, automatically reconnecting if necessary.# If you want to test connection beforehand, await esme.connect() first.# It will raise an exception if connection is not successfull -# typically SmppError, or one of transport errors (OSError, TimeoutError, socket.error etc).asyncio.create_task(esme.start())# Give it some time to send messages.awaitasyncio.sleep(20)# Stop ESME.awaitesme.stop()if__name__=="__main__":loop=asyncio.get_event_loop()loop.run_until_complete(main())loop.close()Quick user guideYour application interacts with ESME via three interfaces: broker, correlator and hook.Broker is a FIFO queue in which your application puts messages. ESME retrieves messages
from the broker and sends them to SMSC. Any type of SMPP message can be queued, but it really
only makes sense forSubmitSm(outgoing SMS). SubclassAbstractBrokerin order to put and
get messages from persistent storage. The library providesjson_encodeandjson_decodeconvenience methods which can be used to convert messages to/from JSON. Again, while any message
can be serialized, it probably only makes sense forSubmitSm, and possiblyDeliverSm.Correlator is an interface that does two types of correlation:Outgoing SMPP requests are correlated with received responses.Outgoing SMS messages (SubmitSm) are correlated with delivery receipts (DeliverSm).Delivery receipts may be received days after original message is sent, so this type of
correlation should be persisted. SubclassSimpleCorrelatorand overrideput_deliveryandget_deliverymethods. If you want to implement more efficient request/response correlation,
subclassAbstractCorrelatorand also overridegetandputmethods.Hook is an interface with three async methods:sending: Called before sending any message to SMSC.received: Called after receiving any message from SMSC.send_error: Called if error occured while sending a SubmitSm.SubclassAbstractHookand implement all three methods. The latter two are essential for
reliable message tracking.Incoming message flowReceiving messages is straightforward. Thereceivedhook will be called. If thesmpp_messageparameter is of typeDeliverSmand itsis_receiptmethod returnsFalse,
it is an incoming SMS. Store it as appropriate.Outgoing message flowSending messages is a lot more involved.Create aSubmitSmmessage with uniquelog_idand optionallyextra_dataparameters.
Any message related to this message will have the samelog_idandextra_data,
provided that correlator did its job.Enqueue the message in broker.If message could not be sent,send_errorhook will be called. Original message is available
insmpp_messageparameter. Theerrorparameter contains exception that occured.ValueError indicates that the message couldn’t be encoded to PDU (probably invalid parameters).Transport errors (OSError and its descendants) indicate a network problem.TimeoutError indicates that the response from SMSC was not received within timeout.
Timeout duration depends on correlator implementation.Whichever error occured, the message will not be re-sent automatically.
User application must implement retry mechanism, if required.If the SMSC does respond, check the response inreceivedhook.
Thesmpp_messageparameter will be either:SubmitSmResp- Ifcommand_statusmember is anything other thanSmppCommandStatus.ESME_ROK, the request has been rejected by SMSC.GenericNack- The request was not understood by SMSC, probably due to network error.Again, if the message was rejected, it will not be re-sent automatically.If the request was accepted, a delivery receipt should arrive after some time.
Inreceivedhook, look forDeliverSmmessage whoseis_receiptmethod
returnsTrue. Then useparse_receiptmethod to get a dictionary with parsed data.
Receipt structure is SMSC-specific, but it usually has the following items:{'id':str# Message ID allocated by the SMSC when submitted.'sub':int# Number of short messages originally submitted.'dlvrd':int# Number of short messages delivered.'submit date':datetime# The time and date at which the message was submitted.'done date':datetime# The time and date at which the message reached its final state.'stat':str# The final status of the message.'err':str# Network specific error code or an SMSC error code.'text':str# The first 20 characters of the short message.}Thestatparameter should have one the following values:DELIVRD- Message is delivered to destination.EXPIRED- Message validity period has expired.DELETED- Message has been deleted.UNDELIV- Message is undeliverable.ACCEPTD- Message is in accepted state.UNKNOWN- Message is in invalid state.REJECTD- Message is in a rejected state.For more details, checkSMPP specification.Example hook implementation:fromaiosmpplibimportAbstractHook,SmppCommandStatusfromaiosmpplibimportDeliverSm,SubmitSm,SubmitSmResp,GenericNack,SmppMessage,TrackableclassMyHook(AbstractHook):asyncdef_save_result(self,msg:str,smpp_message:Trackable)->None:log_id:str=smpp_message.log_idextra_data:str=smpp_message.extra_data# Save data to databaseasyncdefsending(self,smpp_message:SmppMessage,pdu:bytes,client_id:str)->None:pass# Or trace logasyncdefreceived(self,smpp_message:Optional[SmppMessage],pdu:bytes,client_id:str)->None:ifisinstance(smpp_message,GenericNack):awaitself._save_result('Sending failed',smpp_message)# Requeue if desiredifisinstance(smpp_message,SubmitSmResp):ifsmpp_message.command_status==SmppCommandStatus.ESME_ROK:awaitself._save_result('Message sent',smpp_message)else:awaitself._save_result('Sending failed',smpp_message)# Requeue if desiredelifisinstance(smpp_message,DeliverSm):ifsmpp_message.is_receipt():# This is a delivery receiptreceipt:Dict[str,Any]=smpp_message.parse_receipt()final_status:str=receipt.get('stat','')iffinal_status=='DELIVRD':msg:str='Delivered to handset'eliffinal_status=='EXPIRED':msg:str='Message expired'eliffinal_status=='DELETED':msg:str='Message deleted by SC'eliffinal_status=='UNDELIV':msg:str='Message undeliverable'eliffinal_status=='ACCEPTD':msg:str='Message accepted'eliffinal_status=='REJECTD':msg:str='Message rejected'else:msg:str='Unknown status'awaitself._save_result(msg,smpp_message)else:pass# This is an incoming SMS# Process and save to databaseasyncdefsend_error(self,smpp_message:SmppMessage,error:Exception,client_id:str)->None:ifisinstance(smpp_message,SubmitSm):awaitself._save_result('Sending failed',smpp_message)# Requeue if desiredBug ReportingBug reports and feature requests are welcome viaGithub issues.