Chapter 19. E-mail Handling

Table of Contents

1. Sending an Email Message
2. Sending Email Asynchronously
3. Managing an Email Queue

This chapter discusses the tools offered by Biferno to handle email from a script using the methods of the smtp class.

1. Sending an Email Message

The simplest way to send an email message from a Biferno script is by using the SendMail static method, with the following prototype:

static string SendMail(string host, string from, string to, string text)
    

The host parameter specifies the name or address of the SMTP server we want to use to send our email. The from parameter specifies the email address to be used as the address of the sender of the message. The to parameter is the email address of the receiver, i.e. the person we want to address the message to. The text parameter is the message text, possibly including part of the SMTP header (in particular the subject field).

The following script shows how to use the SendMail method:

<?
	email_host = "mailserver.mydomain.com"
	email_from = "me@mydomain.com"
	email_to = "him@hisdomain.com"
	email_text = "Subject: SendMail Test\r\n\r\n"
	email_text += "Test email body."

	status = smtp.SendMail(email_host, email_from, email_to, email_text)
?>
    

Notice that in our example the message text contains a first line starting with the word "Subject" followed by the ":" character (semicolon) and by the subject text. This line is the header of our message. The body of the message must be separated from the header by two new lines (each of them being the combination of the characters CR, carriage return, and LF, line feed).

In the message header it is also possible to insert other fields that are specific to the SMTP header, such as "Cc:" (carbon copy) and others, separated by a single new line character combination (CR+LF). The exact syntax is therefore: field_name + semicolon + value + CR + LF.

The SendMail method returns a string containing the description of an error (or warning) generated by the SMTP server. This allows us to verify if the message has been correctly sent. If errors in the parameters are discovered (e.g. a wrong SMTP server name), the SendMail method generates an error of the smtp class.

The SendMail method sends the message synchronously, which implies that script execution may block for several seconds waiting for an answer from the SMTP server. Additionally, no resend attempts take place if sending a message fails on the first attempt, and the email will not be sent to destination (unless the script is run again).

2. Sending Email Asynchronously

The smtp class also supports asynchronous sending of email, i.e. without waiting for an answer from the SMTP server.

To send asynchronous email messages, the smtp class makes the SendMailAsync static method available, with prototype:

static string SendMailAsync(string host, string from, string to, 
  	string text, string filePath, unsigned minutes)
    

This method has two additional optional parameters with respect to the SendMail method. The filePath parameter specifies the path of the spool file where Biferno temporarily stores the message while it waits to be sent to the server. If this parameter is not passed, Biferno uses a default name for the spool file. The minutes parameters defines the timeout, in minutes, after which Biferno will stop trying to send the message in case of repeated errors. The default timeout is five minutes.

The SendMailAsync works in a simple fashion. When the method is called, the email message is stored in a special format (XML) within the file specified by the filePath parameter. The ".xml" extension is added automaticaly to the file name. Once this step is completed, Biferno continues to process the current script with no further interruption, while another process running in parallel has the task of asynchronously sending the queued messages.

If an error occurs, Biferno will try to send the message again at regular intervals, until the timeout specified by the minutes parameter is reached. For this reason using the SendMailAsync method not only makes script execution faster, but also increases the likelihood that a message can be successfully sent in presence of temporary error conditions.

All attempts to send a message, i.e. the conversational protocol exchanges with the SMTP server, are stored in a log file in the same directory as the message file. The log file has the same name of the message file with the extension ".log" appended. The message file, together with the corresponding log file, is deleted from disk after the message has been successfully sent or after the timeout period expires.

The SendMailAsync method returns a string containing the file path of the message, including the name with which it has been stored on disk. The filename is returned because a default is used if no parameter was passed, because the ".xml" extension is appended automatically, and finally because, if the file specified by the filePath parameter already exists, the method appends a progressive number to the file name.

The following script shows how to use the SendMailAsync method. The script sends a message to a number of email addresses stored in an array, which could be filled e.g. by a database query.

<?
email_host = "mailserver.mydomain.com"
email_from = "me@mydomain.com"
email_text = "Subject: SendMailAsync Test\r\n\r\n"
email_text += "Test email body."
email_timeout = 120 // Two hours

arr_email = array("user1@domain1.com", "user2@domain2.com", 
		"user3@domain3.com", "user4@domain4.com")
n_email = arr_email.dim
email_sent = 0

for (i = 1; i <= n_email; i++)
{
	email_to = arr_email[i]
	if (email_to.IsEMail())
	{
		email_sent++
		email_file = "emails/email_" + email_sent
		smtp.SendMailAsync(email_host, email_from, email_to, 
			email_text, email_file, email_timeout)
	}
}	
?>
    

3. Managing an Email Queue

The smtp class allows to verify, within the set timeout, individual email messages that are still stored on disk because of a persistent error condition that caused automatic send attempts to fail (recall that messages are deleted from disk anyway after timeout).

The ParseMailFile static method reads a file containing an email message in the format generated by the SendMailAsync method and returns an associative array containing the various elements of the message. This method has the following prototype:

static array ParseMailFile(string filePath)
    

The filePath parameter specifies the path of the file to be examined. The associative array returned by this method contains six elements with the following indices:

sm_timeout 
sm_host
sm_from
sm_to
sm_body
sm_last_error
sm_creation_secs
    

The element with index sm_last_error contains the description of the last error generated by an attempt to send the message.

The element with index sm_creation_secs provides the date (in seconds from time zero, see Chapter 14, Date and Time Functionality) of the creation of the email file.

The following script shows how to verify the existence of email files corresponding to unsent messages in the directory specified in the path passed to the SendMailAsync method and, at the same time, how to offer to the application user the option of manual sending.

<html>
<body>
	<h2>Queued messages list</h2>
<?
function int ListMessages(string filePath, long variant)
{
	if (msg = smtp.ParseMailFile(filePath))
	{
		global queued_msg++
		print("<b>Message nr. " + global queued_msg + "</b><br>")
		print("From: " + msg["sm_from"] + "<br>")
		print("To: " + msg["sm_to"] + "<br>")
		arr_msg = msg["sm_body"].ToArray("\r\n")
		print(arr_msg[1] + "<br>") // Print subject
		print("<a href=send_message.bfr?msg_path=" + filePath.UrlEncode() + \
				"><b>Send now</b></a><br><br>")
	}
	return 0
}

global queued_msg = 0
email_dir = folder("emails/")
email_dir.Walk("", false, "ListMessages")
if (global queued_msg == 0)
	print("No messages queued.")
?>
</body>
</html>
    

Using the Walk method of the folder class, the script examines the content of the directory where files generated by the SendMailAsync method are stored and, for each such file, displays the from, to and subject message fields, associated to a link to another script ("send_message.bfr") that sends messages in a synchronous fashion.

A very simplified version of the "send_message.bfr" script could look as follows:

<html>
	<body>
<?
	status = smtp.SendMailFile(msg_path)
	if (status == "")
		print("Message sent.")
	else
		print("ERRORE! " + status)
?>
	</body>
</html>
    

This script accepts as input the path of the file corresponding to the message to be sent and sends it using the SendMailFile static method, with prototype:

static string SendMailFile(string filePath)
    

This method verifies the content of the file corresponding to the filePath path, assembles the message, sends it synchronously, and, if no errors occur, deletes the file. If an error occurs, a string containing the error description is returned and the file is not removed from disk. The SendMailFile method also generates a log file, which is deleted along with the message file if sending is successful.