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 [email protected] -f [email protected]
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 -t -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: [email protected]
To: [email protected]
Cc: [email protected]
Bcc: [email protected]
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
if pBcc is not empty then
put "Bcc:" && pBcc & return after tMsg
end if
put pMsg & return after tMsg
Note that the BCC addresses are now handled here, the sendmail -t flag checks these headers and processes all the different kinds of addresses.
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 and bcc addresses in here
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 pBcc is not empty then
put "Bcc:" && pBcc & 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
-- The -t flag sets Sendmail to check tMsg for To, CC, and BCC fields
get shell("echo" && wrapQ(shellEscape(tMsg)) && "| /usr/sbin/sendmail -t -f" && \
&& wrapQ(shellEscape(pFrom)))
end mail
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
Tim
Hi, Is this still the optimum method of sending an email within Livecode server? Thanks.
Matthias Rebbe
Hi Tim,
in case you've a commercial license of Livecode Server you could use tsNET external for it.
I've created a simple .lc script that uses tsNET to downloads the LC release notes of lc 9.6.1 from the LC Server and outputs it directly so the browser downloads it right away.
See https://livecode.dermattes.de/tsnet/tsnet.lc
The script can bee seen with this link
https://livecode.dermattes.de/tsnet/tsnet.lc.txt
Important is that you first run tsInit to intialize tsNET on LC server.
Hope this helps.
Regards,
Matthias