XQuery/Sending E-mail

Motivation edit

You want to send an e-mail message from within an XQuery. This is frequently done when a report has finished running or when a key event such as a task update has been done.

Method edit

eXist provides a simple interface to e-mail.

Creating a mail session edit

  let $mail-handle := mail:get-mail-session
  (
    <properties>
      <property name="mail.smtp.host" value="smtp.your-domain.com"/>
      <property name="mail.smtp.port" value="25"/>
      <property name="mail.smtp.auth" value="false"/>
      <property name="mail.smtp.allow8bitmime" value="true"/>
    </properties>
  )

Format of the send-email function edit

  mail:send-email($email as element()+, $server as xs:string?, $charset as xs:string?) xs:boolean+ where $email 

The email message is in the following format:

<mail>
   <from/>
   <reply-to/>
   <to/>
   <cc/>
   <bcc/>
   <subject/>
   <message>
     <text/>
     <xhtml/>
   </message>
   <attachment filename="" mimetype="">xs:base64Binary</attachment> 
</mail>
  $server	The SMTP server. If empty, then it tries to use the local sendmail program.
  $charset	The charset value used in the "Content-Type" message header (Defaults to UTF-8)

Sample Code edit

xquery version "1.0";

(: Demonstrates sending an email through Sendmail from eXist :)

declare namespace mail="http://exist-db.org/xquery/mail";

declare variable $message {
  <mail>
    <from>John Doe &lt;sender@domain.com&gt;</from>
    <to>recipient@otherdomain.com</to>
    <cc>cc@otherdomain.com</cc>
    <bcc>bcc@otherdomain.com</bcc>
    <subject>A new task is waiting your approval</subject>
    <message>
      <text>A plain ASCII text message can be placed inside the text elements.</text>
      <xhtml>
           <html>
               <head>
                 <title>HTML in an e-mail in the body of the document.</title>
               </head>
               <body>
                  <h1>Testing</h1>
                  <p>Test Message 1, 2, 3</p>
               </body>
           </html>
      </xhtml>
    </message>
  </mail>
};

if ( mail:send-email($message, 'mail server', ()) ) then
  <h1>Sent Message OK :-)</h1>
else
  <h1>Could not Send Message :-(</h1>

Example of JSON API edit

Many e-mail services also provide a JSON API. For example, Mandrill has an e-mail service that allows you to send your first 12,000 emails per month for free. After you register for an account they will provide you with an API key. This API key can be used with the following to send an e-mail message:

xquery version "3.0";

module namespace mandrill="http://haptixgames.com/nosql-asset/mandrill";
import module namespace xqjson="http://xqilla.sourceforge.net/lib/xqjson";
import module namespace functx = "http://www.functx.com";
declare namespace httpclient="http://exist-db.org/xquery/httpclient";

declare variable $mandrill:message-send := 'https://mandrillapp.com/api/1.0/messages/send.json';

declare function mandrill:send($api-key as xs:string, 
                                $from-email as xs:string, 
                                $from-name as xs:string, 
                                $to-email as xs:string, 
                                $subject as xs:string,
                                $body-text as xs:string)
{
    let $email-xml := 
        <json type="object">
            <pair name="key"  type="string">{$api-key}</pair>
            <pair name="message"  type="object">
                <pair name="text"  type="string">{$body-text}</pair>
                <pair name="subject"  type="string">{$subject}</pair>
                <pair name="from_email"  type="string">{$from-email}</pair>
                <pair name="from_name"  type="string">{$from-name}</pair>
                <pair name="to"  type="array">
                    <item type="object">
                        <pair name="email"  type="string">{$to-email}</pair>
                    </item>
                </pair>
            </pair>
         </json>
    
    let $response := httpclient:post(xs:anyURI($mandrill:message-send), xqjson:serialize-json($email-xml), false(), ())
    
    return
    if($response/@statusCode/string() eq "200")
    then 
    (
        let $body := xqjson:parse-json(util:base64-decode($response/httpclient:body/text()))
        
        return
        if($body/item/pair[@name/string() eq 'status']/text() eq 'sent')
        then true()
        else false()
    )
    else false()
};

Below is a refined version of the above Mandrill script, which does not use the XQJSON module to serialize the XML message to JSON.

xquery version "3.0";

(:~
: This module sends emails via Mandrill API.
:
: @author HaptixGames
: @version 1.0
:
:)

module namespace mandrill2="http://haptixgames.com/shared/modules/mandrill2";

import module namespace functx = "http://www.functx.com";

declare namespace httpclient="http://exist-db.org/xquery/httpclient";

(:~  
 : Creates an email message from inputs. 
 :)
declare %private function mandrill2:create-message(
    $api-key as xs:string, 
    $subject as xs:string,
    $body,
    $attachments as element(attachment)*,
    $from-email as xs:string,
    $from-name as xs:string,
    $to-addresses)
        as element(json)
{
    <mandrill>
        <key>{$api-key}</key>
        <async>false</async>
        <message>
            {
                if($body instance of element()*)
                then <html>{serialize($body)}</html>
                else <text>{$body}</text>
            }
            <subject>{$subject}</subject>
            <from_email>{$from-email}</from_email>
            <from_name>{$from-name}</from_name>
            {
                for $address in $to-addresses
                return
                    if($address instance of element(address))
                    then
                        <to>
                            <email>{$address/email/text()}</email>
                            <name>{$address/name/text()}</name>
                            <type>to</type>
                        </to>
                    else
                        <to>
                            <email>{$address}</email>
                            <type>to</type>
                        </to>
            }
            {   (: fake out serializer to write array :)
                if(count($to-addresses) eq 1)
                then <to/>
                else()
            }
            {
                if($attachments)
                then
                    (
                        for $attachment in $attachments
                        return
                            <attachments>
                                <type>{$attachment/mime-type/text()}</type>
                                <name>{$attachment/file-name/text()}</name>
                                <content>{$attachment/content/text()}</content>
                            </attachments>
                    )
                else ()
            }
            {   (: fake out serializer to write array :)
                if(count($attachments) eq 1)
                then <attachments/>
                else()
            }
        </message>
    </mandrill>
};

(:~ 
 : Executes Mandrill APi send.
 : 
 : @param $api-uri Mandrill API URI (default:https://mandrillapp.com/api/1.0/messages/send.json)
 : @param $api-key your Mandrill API key
 : @param $body xs:string or XML fragment
 : @param $attachments sequence in the form of <attachment><mime-type>text/plain</mime-type><file-name>some-file.txt</file-name><content>file-binary-content</content></attachment>...
 : @param $to-addresses sequence of xs:string email addresses or sequence of <address><email>some@domain.com</email><name>some guy</name></address>...
 :)
declare function mandrill2:send(
    $api-uri as xs:anyURI,
    $api-key as xs:string,
    $from-email-name as xs:string,
    $from-email-address as xs:string,
    $subject as xs:string,
    $body,
    $attachments as element(attachment)*,
    $to-addresses) 
{
    let $message-xml :=
        mandrill2:create-message
        (
            $api-key, 
            $subject,
            $body,
            $attachments,
            $from-email-address,
            $from-email-name,
            $to-addresses
        )
   
    let $message-json :=
        util:serialize($message-xml,'method=json')
    
    let $response := 
        httpclient:post
        (
            $api-uri, 
            $message-json, 
            false(), 
            (
                <headers>
                    <header name="Content-Type" value="application/json"/>
                </headers>
            )
        )
    
    return
    if($response/@statusCode/string() eq "200")
    then true()
    else false()
};

This example was provided by Chris Misztur on Sept. 21, 2013.

References edit