Sending Emails From LiveCode Server Scripts

A common task among web scripts is the sending of emails, be it for verifying user accounts or confirming online orders.  This lesson covers the sending of emails from LiveCode Server scripts.

Note: This lesson only applies on OSX and Linux servers.

Calling Sendmail From LiveCode

The easiest way to send emails from a LiveCode Server script is to shell out to a command line application.  In this example, we will use sendmail.  Sendmail can be called from the command line in the following manner:

[user@user: /]$ sendmail to@address.com -f from@address.com

Users can then type in their message subject and body.  We will create a LiveCode command that will wrap sendmail, calling it via the shell function, allowing users to send emails from LiveCode Server scripts.  Our command will have the following prototype:

-- mail
--
-- Emails the given message to the recipients specified.
-- Each address passed can have a name  attached in the form "name <address>".
-- Addresses can be passed as comma separated lists.
-- Attachements can be added by passing an array (interger indexed or otherwise).
-- with each attachment itself being an array.
--
-- pTo		- The addresses to send the message to
-- pSub		- The message subject
-- pMsg		- The message body
-- pFrom	- The address of the message sender
-- pCc		- Any cc addresses
-- pBcc		- Any Bcc addresses
-- pHtml		- Boolean, if the message is to be sent as html
-- pAtts		- Array of all attachments to send, each attachment of the form:
--					* name:  the name of the attachment
--					* path:  the absolute path to the attachment
--					* type:  the mime type of the attachment, defaults to 
--					application/octet-stream
--

command mail pTo, pSub, pMsg, pFrom, pCc, pBcc, pHtml, pAtts

First of all, we look at the actual calling of sendmail.  As mentioned above, we will call it via the shell function.  Instead of the user typing the message in after calling sendmail, we will pipe our message directly to sendmail.

get shell("echo" && wrapQ(shellEscape(pMsg)) && "| /usr/sbin/sendmail"  && wrapQ(shellEscape(pTo)) && "-f" && wrapQ(shellEscape(pFrom)))

Note the use of helper functions "wrapQ" and "shellEscape".  These functions format strings for use in the shell function.  "wrapQ" just pre and post fixes the passed string with quotation marks while "shellEscape" escapes any special characters.

-- escape shell characters: use this function before passing data to the shell
function shellEscape pText
	repeat for each char tChar in "\`!$" & quote
		replace tChar with "\" & tChar in pText
	end repeat
	return pText
end shellEscape

-- wrap quotes around text
function wrapQ pText
	return quote & pText & quote
end wrapQ

Formatting Email Headers

So, our command now sends bare bones emails.  In order for subject, from, to and cc parameters to work correctly, we must add the appropriate headers to our message.  To do this, we just need to prefix our message with the following header data: 

From: from@address.com
To: to@address.com
Cc: cc@address.com
Subject: Message Subject
We will do this by creating a new variable for placing the header and message data into:
local tMsg
put "From:" && pFrom & return & "To:" && pTo & return & "Subject:" && pSub & return into tMsg
if pCc is not empty then
	put "Cc:" && pCc & return after tMsg
end if
put pMsg & return after tMsg

Note that the BCC addresses are not handled here.  Instead, we just send a copy of the message to the BCC addresses:

if pBcc is not empty then
	get shell("echo" && wrapQ(shellEscape(tMsg)) && "| /usr/sbin/sendmail"  && \
	wrapQ(shellEscape(pBcc)) && "-f" && wrapQ(shellEscape(pFrom)))
end if

The next parameter in our command's prototype is "pHtml".  This will be a boolean value that will determine if the email is to be sent as plain text or html.  To do this, we just add to our header (before we add the message body) the content type:

if pHtml is true then
	put "Content-Type: text/html;" & return & return after tMsg
else
	put "Content-Type: text/plain;" & return & return after tMsg
end if

Adding Attachments

Our final parameter handles any attachments.  We intend attachments to be passed in the form of an array, with an element for each attachment.  Each attachment will be an array itself, with three elements, the attachments name, path and mime type.  An attachment array will take the following form:

put "/home/text_file.txt" into tAtts[1]["path"]
put "text_file.txt" into tAtts[1]["name"]
put "text/plain" into tAtts[1]["type"]
put "/home/pdf_file.pdf" into tAtts[2]["path"]
put "pdf_file.txt" into tAtts[2]["name"]
put "application/pdf" into tAtts[2]["type"]

For more information about mime type visit http://www.w3schools.com/tags/att_script_type.asp.  In order to send these attachments, we must separate them out from the body of our email.  We do this by defining the email as being multipart.  That entails that the email body will have multiple sections, one being our message body, the remainder our attachments.  Each section will be split by a unique boundary string.  We do this by stating in our email will be multipart and defining our boundary string in the header:

if pAtts is an array then
	local tBoundary
	put "boundary" & the seconds into tBoundary
	put "MIME-Version: 1.0" & return & "Content-Type: multipart/mixed; boundary=" & \
	wrapQ(tBoundary) & return & "--" & tBoundary & return after tMsg
end if

Next we need to add our attachments to the email, after the message body.  We do this by adding the base 64 encoded contents of the attachment on to the end of our message, remembering to separate each with our boundary string:

if pAtts is an array then
	put "--" & tBoundary & return after tMsg
	repeat for each element tAtt in pAtts
		put "Content-Type:" && tAtt["type"]  & "; name=" & wrapQ(tAtt["name"]) & ";" & \
		return & "Content-Transfer-Encoding: base64" & return & return & \ 
		base64Encode(URL ("binfile:" & tAtt["path"])) & return & "--" & tBoundary & \
		return after tMsg
	end repeat
end if

The Complete Command

So that's it, our command is now complete.  With all our sections added in, it should look something like the following:

-- mail
--
-- Emails the given message to the recipients specified.
-- Each address passed can have a name  attached in the form "name <address>".
-- Addresses can be passed as comma separated lists.
-- Attachements can be added by passing an array (interger indexed or otherwise).
-- with each attachment itself being an array.
--
-- pTo		- The addresses to send the message to
-- pSub		- The message subject
-- pMsg		- The message body
-- pFrom	- The address of the message sender
-- pCc		- Any cc addresses
-- pBcc		- Any Bcc addresses
-- pHtml		- Boolean, if the message is to be sent as html
-- pAtts		- Array of all attachments to send, each attachment of the form:
--					* name:  the name of the attachment
--					* path:  the absolute path to the attachment
--					* type:  the mime type of the attachment, defaults to 
--					application/octet-stream
--

command mail pTo, pSub, pMsg, pFrom, pCc, pBcc, pHtml, pAtts
	local tMsg
	-- build the message header, adding the from, to and subject details
	-- we also put any cc addresses in here, but not bcc (bcc addresses hidden)
	put "From:" && pFrom & return & "To:" && pTo & return & "Subject:" && pSub & \
	return into tMsg	if pCc is not empty then
		put "Cc:" && pCc & return after tMsg
	end if
	-- if there are any attachments, we must send this email as multipart
	-- with the message body and each attachment forming a part
	-- we do this by specifying the message as multipart and generating a unique boundary
	if pAtts is an array then
		local tBoundary
		put "boundary" & the seconds into tBoundary
		put "MIME-Version: 1.0" & return & "Content-Type: multipart/mixed; boundary=" & \
		wrapQ(tBoundary) & return & "--" & tBoundary & return after tMsg
	end if
	-- add the actual message body, setting the content type appropriatly
	if pHtml is true then
		put "Content-Type: text/html;" & return & return after tMsg
	else
		put "Content-Type: text/plain;" & return & return after tMsg
	end if
	put pMsg & return after tMsg
	-- add each attachment as a new part of the message, sepearting using 
	-- the generated boundary
	if pAtts is an array then
		put "--" & tBoundary & return after tMsg
		repeat for each element tAtt in pAtts
			if there is a file tAtt["path"] then
				if tAtt["type"] is empty then
					get "application/octet-stream"
				else
					get tAtt["type"]
				end if
				put "Content-Type:" && it & "; name=" & wrapQ(tAtt["name"]) & ";" & \
				return & "Content-Transfer-Encoding: base64" & return & return & \
				base64Encode(URL ("binfile:" & tAtt["path"])) & return & "--" & \
				tBoundary & return after tMsg
			end if
		end repeat
	end if
	-- send the mail by piping the message we have just built to the sendmail command
	-- we must also send a copy of the message to the bcc addresses
	get shell("echo" && wrapQ(shellEscape(tMsg)) && "| /usr/sbin/sendmail"  && \
	wrapQ(shellEscape(pTo)) && "-f" && wrapQ(shellEscape(pFrom)))
	if pBcc is not empty then
		get shell("echo" && wrapQ(shellEscape(tMsg)) && "| /usr/sbin/sendmail"  && \
		wrapQ(shellEscape(pBcc)) && "-f" && wrapQ(shellEscape(pFrom)))
	end if
end mail

7 Comments

Shedo Surashu

nice ^^

Tim Selander

Hi, I'm a newbie at rev and know zero about Unix, shell and sendmail commands. Having a bit of a hard time following this. I don't need attachments, but do need to add these headers:

Content-Type: text/plain; charset=iso-2022-jp
Content-Transfer-Encoding: 7bit

I have a variation of this script working (with help of Sarah Reichelt) but can you show me what the script would look like to add these two headers? Thanks.

Mark Wieder

Change the section of code that sets the headers to read:

if pHtml is true then
put "Content-Type: text/html; charset=iso-2022-jp " & return after tMsg
else
put "Content-Type: text/plain; charset=iso-2022-jp " & return after tMsg
end if
put "Content-Transfer-Encoding: 7bit" & return & return after tMsg

Torsten

Correction needed: in the sentence "We do this by define g the email as being multipart. " the "g" is not intended :-)

Elanor Buchanan

Hi Torsten

Thanks for bringing that to our attention, I have corrected the text in the lesson.

Elanor

Torsten

Important typo regarding PDF encoding: https://forums.livecode.com/viewtopic.php?f=15&t=10422&p=48072

Instead of :

"Content-Transfer-Encoding: Base64;" & return & return &

it should be :

"Content-Transfer-Encoding: Base64" & return & return

without the ";" after Base64

Thats right, it works this way, please correct the code.

Kind regards,
Torsten

Elanor Buchanan

Thanks Torsten, I have updated the lesson.

Kind regards

Elanor

Add your comment

E-Mail me when someone replies to this comment