Sms alerts for incoming e-mails

Introduction
This is a set of classes that will allow a user to receive SMS alerts for incoming e-mails. It was written with Caché 5.0, running on a Windows 2000 server. It is my first attempt to use Caché Objects for a real-world application, so any suggestions to refactor it would be most welcome.

MailLog class
Include %occStatus

Class User.MailLog Extends (%Persistent, %XML.Adaptor) [ ClassType = persistent, ProcedureBlock ] {

Property Server As %String [ Required, SqlColumnNumber = 2 ]; Property Account As %String [ Required, SqlColumnNumber = 3 ]; Property UniqueId As %String [ Required, SqlColumnNumber = 4 ]; Index UIDInx On (Server, Account, UniqueId) [ Unique ];

Property From As %String(MAXLEN = 60) [ SqlColumnNumber = 5, SqlFieldName = Sender ]; Property To As %String(MAXLEN = 60) [ SqlColumnNumber = 6, SqlFieldName = Receiver ]; Property Date As %String [ SqlColumnNumber = 7, SqlFieldName = DateSentText ]; Property TimeStamp As %TimeStamp [ SqlColumnNumber = 8, SqlFieldName = DateSent ]; Property TimeStampFull As %String [ Calculated, SqlColumnNumber = 9, SqlFieldName = DateSentFull ]; Property Subject As %String(MAXLEN = 100) [ SqlColumnNumber = 10 ]; Property Message As %String(MAXLEN = 250) [ SqlColumnNumber = 11 ]; Property Size As %Integer [ SqlColumnNumber = 12, SqlFieldName = MessageSize ]; Property Alert As %Integer [ SqlColumnNumber = 13 ]; Property Sms As SmsMessage [ SqlColumnNumber = 14 ]; Index AlertInx On Alert [ Type = bitmap ];

ClassMethod Actions {	Set Time=$Piece($HoroLog,",",2) // if time is between 8am and 10pm, check if there are any text messages to be sent If (Time'<28800) & (Time'>79200) { Set rset = ##class(%ResultSet).%New("MailLog:SmsPending") Do rset.Execute While (rset.Next) { Set msg = ##class(MailLog).%OpenId(rset.Data("ID")) Set sc=##class(SmsMessage).SendTextXml(msg.Sms.To, msg.Sms.Message) If sc=1 { // mark as sent Set msg.Alert=1 Set msg.Sms.Time=$Piece($HoroLog,",",2) } Else { // mark as failure Set msg.Alert=3 }			Set sc = msg.%Save }		Do rset.Close }	// 	Do ..CheckMail("PopServer","UserName1","PassWord1") Do ..CheckMail("PopServer","UserName2","PassWord2") }

Query SmsPending As %SQLQuery(CONTAINID = 1) { SELECT %ID FROM MailLog WHERE Alert = 2 }

Method TimeStampFullGet As %String {	Set DateTimeH=$ZDateTimeH(..TimeStamp,3) Set Day=$ZDate(+DateTimeH,11) Set DateTime=$ZDateTime(DateTimeH,2,2) Quit Day_", "_DateTime }

ClassMethod CheckMail(PopServer As %String, UserName As %String, PassWord As %String) {	Set pop3=##class(%Net.POP3).%New Set sc=pop3.Connect(PopServer,UserName,PassWord) If $$$ISERR(sc) Quit //	// truncate user name Set UserName=$Piece(UserName,"@") //	// walk through message array Set sc=pop3.GetMessageUIDArray("",.uids),y="" For { Set y=uids.Next(y) If y="" Quit Set uid=uids.GetAt(y) //		// don't bother if message details already stored // Index UIDInx On (Server, Account, UniqueId) [ Unique ]; If ..UIDInxExists(PopServer, UserName, uid) Continue //		// retrieve the whole e-mail Set sc=pop3.Fetch(y,.mail) //		// check the date Set DateTimeH=mail.GetLocalDateTime If $ZAbs(DateTimeH-$HoroLog)>2 Set DateTimeH=$HoroLog //		// now extract the body text Set body="" Do ..GetPartMailBody(mail, 250, .body) //		// store message details Set msg = ##class(MailLog).%New Set msg.Server=PopServer Set msg.Account=UserName Set msg.UniqueId=uid Set msg.From=mail.From Set msg.To=mail.Headers.GetAt("to") Set msg.Date=mail.Date Set msg.TimeStamp=$ZDateTime(DateTimeH,3) Set msg.Subject=mail.Subject Set msg.Message=$ZStrip(body,"<>W") Set msg.Size=mail.MessageSize Set msg.Alert=0 Set sc = msg.%Save If $$$ISERR(sc) { Do $system.Status.DecomposeStatus(sc,.list) Set ^qqTSG("MailLogErr",+$HoroLog,uid)=list(1) Continue }		//		// check against ignore list If msg.From["Gizmo" Continue //		// define sms text message Set crlf=$Char(13,10) Set msg.Sms.Message = "Message from "_$ZStrip($Piece(msg.From,"<"),"<>P")_crlf Set msg.Sms.Message = msg.Sms.Message_"with a subject of "_$Char(34)_msg.Subject_$Char(34)_crlf Set msg.Sms.Message = msg.Sms.Message_"was received on "_msg.TimeStampFull_crlf //		// get number to send to		Set msg.Sms.To="+447890123456" //		// append the body of the e-mail to the text message If msg.Message?.E1A.E Set msg.Sms.Message = msg.Sms.Message_"> "_msg.Message //		// send da text massage, but only between 08h00 and 22h00 Set Time=$Piece($HoroLog,",",2) If (Time'<28800) & (Time'>79200) { Set sc=##class(SmsMessage).SendTextXml(msg.Sms.To, msg.Sms.Message) If sc=1 { // mark as sent Set msg.Alert=1 Set msg.Sms.Time=$Piece($HoroLog,",",2) } Else { // mark as failure Set msg.Alert=3 }		} Else { // mark as deferred Set msg.Alert=2 }		//		// add sms details to log object Set sc = msg.%Save }	//	Set sc=pop3.QuitAndRollback }

ClassMethod GetPartMailBody(msg As %Net.MailMessage, len As %Integer, ByRef text As %String = "", deep As %String = "") {       If msg.IsMultiPart { For i=1:1:msg.Parts.Count { Do ..GetPartMailBody(msg.Parts.GetAt(i), len, .text, deep_"."_i) }       } Else { If '(msg.IsBinary),'(msg.IsBase64),'(msg.IsHTML) { Set stream=msg.TextData Do stream.Rewind Set text=stream.Read(.len) }       }        Quit }

ClassMethod Schedule {	// create an entry on the system task manager // [run task manager (i.e. Do ^TASKMGR) program from %SYS namespace to check] //	Set x=##Class(%SYSTEM.Task).%New Set x.Name="Check Mail" Set x.Description="Check e-mail accounts every five minutes" Set x.ExecuteCode="Do ##Class(MailLog).Actions" Set x.NameSpace=$ZNSpace Set x.DailyFrequency=1			// Several times a day Set x.DailyFrequencyTime=0		// Minutes Set x.DailyIncrement=5			// Every 5 minutes Set x.RescheduleOnStart=1 Do x.%Save }

}

SmsMessage class
To send a SMS (Short message service) message I have used an API provided by Sipgate. Detailed documention for this API can be found here.

Class User.SmsMessage Extends (%SerialObject, %XML.Adaptor) [ ClassType = serial, ProcedureBlock ] {

Property To As %String(MAXLEN = 20) [ SqlFieldName = Receiver ]; Property Message As %String(MAXLEN = 160); Property Time As %Time [ SqlFieldName = TimeSent ];

ClassMethod SendTextXml(Number As %String, Message As %String) As %Boolean {	// check the telephone number If $Extract(Number)="0" Set $Extract(Number)="+44" Set Number=$Translate(Number,"+ ") If $Length(Number)<12 Quit 0 //	// restrict number of texts in a day Set count=$Increment(^qqTSG("SmsCount",+$HoroLog)) If count > 99 Quit 0 //	// replace carriage return, line feed Set crlf=$ZStrip(Message,"*E",,$Char(10,13)) Set crlf=$Extract(crlf,1,2) If crlf=$Char(10,13) Set Message=$Translate(Message,$Char(10,13),"\n") If crlf=$Char(13,10) Set Message=$Translate(Message,$Char(13,10),"\n") //	// truncate the message, if necessary If $Length(Message)>160 Set Message=$Extract(Message,1,160-3)_"..." // 	// define xml message Set writer=##class(%XML.Writer).%New Set writer.Charset="UTF-8" Set writer.Indent=1 Do writer.OutputToString Do writer.RootElement("methodCall") Do writer.Element("methodName") Do writer.WriteChars("samurai.SessionInitiate") Do writer.EndElement Do writer.Element("params") Do writer.Element("param") Do writer.Element("value") Do writer.Element("struct") //	Do ..MemberElement(writer,"RemoteUri","sip:"_Number_"@sipgate.net","string") Do ..MemberElement(writer,"TOS","text","string") Do ..MemberElement(writer,"Content",Message,"string") //	Do writer.EndElement Do writer.EndElement Do writer.EndElement Do writer.EndElement Do writer.EndRootElement Set xml=writer.GetXMLString // 	// set web request details Set req=##class(%Net.HttpRequest).%New Set req.ContentType="text/xml" Set req.ContentCharset="UTF-8" Set req.Server="samurai.sipgate.net" Set req.Port=443 Set req.ProxyHTTPS=1 Set req.ProxyServer="delegate_server" Set req.ProxyPort=89 Set req.Username="your_username" Set req.Password="your_password" //	// add xml payload and post Do req.EntityBody.Write(xml) Set rc=req.Post("/RPC2") //	// check response is okay before returning success code If req.HttpResponse.StatusCode'=200 Quit 0 //	// exit Quit 1 }

ClassMethod MemberElement(writer As %XML.Writer, name As %String, value As %String, datatype As %String) {	Do writer.Element("member") Do writer.Element("name") Do writer.WriteChars(name) Do writer.EndElement Do writer.Element("value") Do writer.Element(datatype) Do writer.WriteChars(value) Do writer.EndElement Do writer.EndElement Do writer.EndElement

}

}