Monday, November 08, 2010 at 12:06 AM.
system.verbs.builtins.tcp.sendMail
on sendMail (recipient="", subject="", message="", sender=user.prefs.mailAddress, cc="", bcc="", host=user.prefs.mailHost, mimeType="text/plain", adrHdrTable=nil, timeOutTicks=60*60, flMessages=true) { <<Changes <<Wed, 16 Jun 1999 00:05:16 GMT by AR <<Added optional adrHdrTable parameter, the address of a table. If an address is specified, its fields are added to the message header, possibly overriding standard header items. <<Added optional timeOutTicks parameter. The default value is 3600 ticks (60 seconds). If the connection is idle for more than the specified number of ticks while waiting for a response from the server, the connection is closed and an error message is generated. <<Added optional flMessages parameter. If it is true (the default), status messages will be displayed in Frontier's About window. The displayed messages are prefixed with the name of this script. Before we return, we clean up the message area. <<Added extensive error checking and reporting code. If the script returns true, it is now safe to assume that the message was accepted by the server. Status messages received from the server indicating that an error has occured are no longer ignored. Whenever an error occurs, we try to shut down the connection gracefully before throwing a scripterror. <<Calls to thread.sleepFor were added to the TCP read/write loops for improved thread-friendliness. <<7/25/99; 8:04:26 AM by DW <<Loop over scripts in user.callbacks.sendmail table. <<11/05/1999 at 12:57:30 AM by AR <<Updated to use 6.1 TCP verbs. <<Cleaned up low-level error messages to conform to "Can't do x because y" scheme. bundle { //loop over callbacks if defined (user.callbacks.tcpSendMail) { local (i); for i = 1 to sizeof (user.callbacks.tcpSendMail) { try { local (adrcallback = @user.callbacks.tcpSendMail [i]); if typeof (adrcallback^) == scriptType { //avoid problems with item #1s if adrcallback^ (recipient, subject, message, sender, cc, bcc, mimetype) { <<callback returns true if it consumed the sendMail operation return (true)}}}}}}; on displayMsg (s) { msg ("tcp.sendMail: " + s)}; local (buffer = "", response = ""); on add (s) { //appends crlf delimited items to our message buffer buffer = buffer + s + cr + lf}; bundle { //build the header and store in buffer local (addedFromTableList = {}); on addFromHeaderTable (s) { if (adrHdrTable != nil) and defined (adrHdrTable^.[s]) { add (nameOf (adrHdrTable^.[s]) + ": " + adrHdrTable^.[s]); addedFromTableList = addedFromTableList + string.lower (s); return (true)}; return (false)}; if not addFromHeaderTable ("X-Mailer") { add ("X-Mailer: " + "UserLand Frontier " + Frontier.version () + " (" + sys.osName () + ")")}; if not addFromHeaderTable ("Mime-Version") { add ("Mime-Version: 1.0")}; if not addFromHeaderTable ("Content-Type") { add ("Content-Type: " + mimeType + "; charset=\"us-ascii\"")}; if not addFromHeaderTable ("Date") { add ("Date: " + date.netStandardString (clock.now ()))}; if not addFromHeaderTable ("To") { if sizeOf (recipient) > 0 { add ("To: " + recipient)}}; if not addFromHeaderTable ("CC") { if sizeOf (cc) > 0 { add ("CC: " + cc)}}; if not addFromHeaderTable ("From") { add ("From: " + sender)}; if not addFromHeaderTable ("Subject") { add ("Subject: " + subject)}; if adrHdrTable != nil { //now add remaining header fields from the table local (i); for i = 1 to sizeOf (adrHdrTable^) { if not (addedFromTableList contains string.lower (nameOf (adrHdrTable^ [i]))) { add (nameOf (adrHdrTable^ [i]) + ": " + adrHdrTable^ [i])}}}; add ("")}; //a blank line before the content is required! bundle { //build message body and store in buffer if not (message contains lf) { //convert line-endings message = string.replaceAll (message, cr, cr+lf)}; message = string.replaceAll (message, "\r\n.\r\n", "\r\n..\r\n"); //replace lines beginning with a period with two periods if message beginsWith ".\r\n" { //if the first line begins with a period message = "." + message}; if not (message endsWith "\r\n") { message = message + "\r\n"}; add (message)}; //add our message text to the buffer on sendCommandToServer (stream, command) { //AR 10/27/1999 <<on sendCommandToServer (stream, command) //pre-6.1 code <<local (chunksize = 4 * 1024) <<local (lastWrite = clock.ticks ()) <<loop //write command to the stream <<try <<if sizeof (command) <= chunkSize <<tcp.writeStream (stream, command) <<sys.systemTask() <<thread.sleepfor (0) <<break <<tcp.writeStream (stream, string.mid (command, 1, chunkSize)) <<command = string.delete (command, 1, chunkSize) <<sys.systemTask() <<thread.sleepfor (0) <<lastWrite = clock.ticks () <<else <<if (tryError contains "10035") and (clock.ticks () < (lastWrite + (60 * 10))) //10035 means the socket buffers are not ready, give them up to 10 seconds. <<sys.systemTask() <<thread.sleepfor (1) //yield this thread for a second to allow normal frontier processing <<else <<try {tcp.closeStream (stream)} <<scriptError ("Error communicating with SMTP host: " + tryError) // re-throw the error <<return (true) try { tcp.writeStringToStream (stream, command, 4096, timeOutTicks / 60)} else { if flMessages { //clean up the message area msg ("")}; try {tcp.abortStream (stream)}; scriptError ("Can't send mail because a connection error occured: " + tryerror)}; return (true)}; on receiveServerResponse (stream) { //AR 10/27/1999 <<on receiveServerResponse (stream) //pre-6.1 code <<local (response = "", bytesPending = 0, ix, linetext) <<local (timeout = clock.ticks () + timeOutTicks) <<loop //read data from the stream <<case tcp.statusStream (stream, @bytespending) <<"DATA" //read data <<response = response + tcp.readStream (stream, bytespending) <<ix = string.patternMatch ("\r\n", response) <<while (ix > 0) //handle multi-line responses <<linetext = string.mid (response, 1, ix-1) <<if string.nthChar (linetext, 4) == ' ' //the final line of the response has a space as the fourth char <<return (linetext) <<response = string.delete (response, 1, ix+1) <<ix = string.patternMatch ("\r\n", response) <<sys.systemTask() <<thread.sleepfor (0) <<timeout = clock.ticks () + timeOutTicks <<continue <<"OPEN" //waiting for data <<if clock.ticks () > timeout <<scriptError ("Can't send mail because the connection to " + host + " timed out.") <<sys.systemTask() <<thread.sleepfor (0) <<continue <<else //connection terminated prematurely <<try {tcp.closeStream (stream)} <<scriptError ("Can't send mail because the connection to " + host + " was terminated prematurely.") <<return ("") //this should never be executed, either we return the server's final response line or we throw a scripterror local (ix, linetext); loop { //read data from the stream try { tcp.readStreamUntil (stream, "\r\n", timeOutTicks / 60, @response)} else { try {tcp.abortStream (stream)}; if flMessages { //clean up the message area msg ("")}; scriptError ("Can't send mail because a connection error occured: " + tryerror)}; ix = string.patternMatch ("\r\n", response); while (ix > 0) { //handle multi-line responses linetext = string.mid (response, 1, ix-1); response = string.delete (response, 1, ix+1); if string.nthChar (linetext, 4) == ' ' { //the final line of the response has a space as the fourth char return (linetext)}; ix = string.patternMatch ("\r\n", response)}}; return ("")}; //this should never be executed, either we return the server's final response line or we throw a scripterror on executeCommand (command, expectResponse = nil) { //send a command to the server and handle the response local (statusline); sendCommandToServer (stream, command + "\r\n"); statusline = receiveServerResponse (stream); if flMessages { displayMsg (statusline)}; if (expectResponse != nil) and not (statusline beginsWith expectResponse) { closeConnection ("Can't send mail because of an unexpected server response. I said \"" + command + "\" and the server replied \"" + statusline + "\".")}; return (true)}; on addRecipient (recipient) { //send recipient command executeCommand ("RCPT TO: <" + string.cleanMailAddress (recipient) + ">", "25")}; //expect status code 250 or 251 on closeConnection (errorstring = "") { //also called in case of unexpected server responses try {executeCommand ("RSET")}; //don't expect a specific response, otherwise we might end up in an infinite loop try {executeCommand ("QUIT")}; try {tcp.closeStream (stream)}; if flMessages { //clean up the message area msg ("")}; if errorstring != "" { //possibly (re)throw scripterrors scriptError (errorstring)}}; local (stream); try { //try to open connection to server stream = tcp.openStream (host, 25)} else { // fatal error scriptError ("Can't send mail because the connection to " + host + " could not be opened: " + tryError)}; bundle { //wait for server's initial connection greeting local (statusline); statusline = receiveServerResponse (stream); if flMessages { displayMsg (statusline)}; if not (statusline beginsWith "220") { closeConnection ("Can't send mail because the server, " + host + ", did not send a valid connection greeting. It said, \"" + statusline + "\".")}}; bundle { //say hello and reset state executeCommand ("HELO " + tcp.addressDecode (tcp.myAddress()), "250"); executeCommand ("RSET", "250")}; bundle { //specify sender and recipients executeCommand ("MAIL FROM: <" + string.cleanMailAddress (sender) + ">", "250"); local (i); if sizeOf (recipient) > 0 { for i = 1 to string.countFields (recipient, ',') { addRecipient (string.nthField (recipient, ',', i))}}; if sizeOf (cc) > 0 { for i = 1 to string.countFields (cc, ',') { addRecipient (string.nthField (cc, ',', i))}}; if sizeOf (bcc) > 0 { for i = 1 to string.countFields (bcc, ',') { addRecipient (string.nthField (bcc, ',', i))}}}; bundle { //send message data executeCommand ("DATA", "354"); sendCommandToServer (stream, buffer); executeCommand (".", "250")}; closeConnection (); return (true)}
This listing is for code that runs in the OPML Editor environment. I created these listings because I wanted the search engines to index it, so that when I want to look up something in my codebase I don't have to use the much slower search functionality in my object database. Dave Winer.