We recently needed to put an emailer together, and thought it would be interesting to use an html alternative - which is more interesting than just plaintext, but also a bit more difficult to implement; what is called a 'multipart/alternative'.
Obyx has a great way of doing a lot of this work for me, which is always helpful, and that's by using the osi:mta, and importantly the obyx message namespace. There's not much to the osi:mta, it's just an instruction to obyx that this is to be an email. Obyx requires an environment variable OBYX_MTA, to point to the sendmail on the box.
OBYX_MTA /usr/sbin/sendmail
Then the basic sendmail envelope is the mta container. The send element has a sender attribute, which should be a legal address that can be handled by your server, and it's where delivery problems will be sent to.
<o:mta xmlns:o="http://www.obyx.org/osi-application-layer">
<o:send sender="errors@myveryowndomain.org" >
<!-- the message itself goes here -->
</o:send>
</o:mta>
The interesting part of email is the actual message itself. In Obyx, this is done using the message namespace, which is also used for HTTP request/response parsing also. Messages are made up of 2 primary sections, a set of headers and a body. (For a great deal of detail on the structure mail messages, RFC 2822 is a good starting point). The most common headers that we want to use are going to be about who the message is from, who it's going to, and a subject line.
The 'From' and 'To' headers (and several others, such as 'Cc', 'Bcc', and so on) have an address format, which typically looks like: "me
<message xmlns="http://www.obyx.org/message">
<header name="Subject" value="email subject here" />
<header name="From" ><address note="me" value="me@myveryowndomain.org" /></header>
<header name="To" ><address note="you" value="you@myveryowndomain.org"/></header>
<body><!-- the message body itself goes here --></body>
</message>
So up to this point, there's not much to it. So the next bit, which is a bit more interesting, is the multipart/alternative body to the email. The body part of a message can also contain messages. This is what makes the structure so flexible. In order to indicate to the mail agents (that send, receive, forward and present an email) it's necessary to identify the content-type of the body, and also to indicate what mime version to use. The latter we do with another header, but the content-type is declared using attributes to the body as follows..
<message xmlns="http://www.obyx.org/message">
[...]
<header name="Mime-Version" value="1.0" />
<body subtype="alternative" type="multipart">[...]
</body>
</message>
Being multipart, we can now use messages for each part of the email. Here we want just two - a plaintext version of the email (for those who don't want to have to view it in html) and an html version also. We don't need any additional headers for that (well, Obyx handles the mimetype headers and so on for us, as well as the multipart syntax). So the entire osi:mta structure for a multipart/alternative mail looks like this:
<o:mta xmlns:o="http://www.obyx.org/osi-application-layer">
<o:send sender="me@myveryowndomain.org" >
<message xmlns="http://www.obyx.org/message">
<header name="Subject" value="From Me to You" />
<header name="From" ><address note="Me" value="me@me.name" /></header>
<header name="To" ><address note="You" value="you@you.name" /></header>
<header name="Mime-Version" value="1.0" />
<body subtype="alternative" type="multipart">
<message><body mechanism="base64" charset="UTF-8" subtype="plain" type="text" >plaintext</body></message>
<message><body mechanism="base64" charset="UTF-8" subtype="html" type="text">markup</body></message>
</body>
</message>
</o:send>
</o:mta>
So that's all there is to a basic mail body! Of course, we now need to fill the holes - including the real addresses, and also the message bodies - both the plain one and the html.
The first thing to do is to choose the view - which will be used for the body. There are some things to know about the way in which email works, and one of them is that it's not a good idea to have to depend upon external links; as there is no guarantee that they will be loaded. Likewise, the actual view needs to be what would normally go into the body of an html document.
Media (images) are a bit complex on emails. There are no guaranteed options, unfortunately. However, we can do what we can to provide inline media, and take advantage of the base64 encoder that Obyx provides. So here we are going to use just the div part of the html.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>mail-body</title>
</head>
<body>
<div>
<h2 style="margin:15px 0 6px 0; font-weight:bold; font-size:180%; font-family:'Century Gothic',serif; font-weight:normal; padding:3px 0 3px 0; background:#DEE5FD; color:#192666;">
<span style="padding: 18px 0 18px 65px;">Title</span>
</h2>
<p style="margin:5px; line-height:1.3em;">placeholder</p>
</div>
</body>
</html>
Okay, so we now need the data (or model) for this mailer. I normally use an sql table for contact details, but an xml document could work just as well. I am going to imagine an SQL table that is made of fields 'name','email','message'; the remainder of the email will be composited with constant values. Let's get to work on the Obyx file itself.
The basic structure is going to be to load up the mail, then iterate through a list of emails to send, and send them. Let's look at that initial structure
<instruction xmlns="http://www.obyx.org" note="initialise email">
<input>
<!-- do initialisation here -->
<iteration operation="sql" note="grab details and send emails out" >
<control value="select name,email,message from mailing" />
<body><!-- compose and send here --></body>
</iteration>
</input>
</instruction>
Okay, so for initialisation, we want to be able to address the html namespace, and also the message namespace; we will need to load the various views up, and set any constant values. We will need to add the mail body to the message after compositing, as we will be base64 encoding it.
<instruction note="load html namespace">
<output space="namespace" value="h" />
<input value="http://www.w3.org/1999/xhtml" />
</instruction>
<instruction note="load message namespace">
<output space="namespace" value="m" />
<input value="http://www.obyx.org/message" />
</instruction>
<instruction note="load html mailview">
<output space="store" value="mv" />
<input space="file" value="mymailer.html" />
</instruction>
<instruction note="load osi/message">
<output space="store" value="msg" />
<input space="file" value="mymailer.osi" />
</instruction>
<instruction operation="append" note="add style attribute with inline gif">
<output space="store" value="mv#//h:h2/h:span/@style/child-gap()" />
<input value="background:url(data:image/gif;base64," />
<input>
<mapping operation="substitute" note="strip newlines for data:">
<domain space="file" value="mymailer.gif" encoder="base64" />
<match><key format="regex" value="\r|\n" /></match>
</mapping>
</input>
<input value=") 0 50% no-repeat;" />
</instruction>
So for composition and mailing, we need to load the fields into the mail, and also into a plaintext, and then send it.
<instruction note="do message">
<output space="store" value="msg#//m:body[@subtype='plain']/text()" encoder="base64" />
<output space="store" value="mv#//h:div/h:p/text()" />
<input space="field" value="message" />
</instruction>
<instruction note="set title of markup and recipient header">
<output space="store" value="msg#//m:header[@name='To']/m:address/@note" />
<output space="store" value="mv#//h:h2/h:span/text()" />
<input space="field" value="name" />
</instruction>
<instruction note="set recipient email">
<output space="store" value="msg#//m:header[@name='To']/m:address/@value" />
<input space="field" value="email" />
</instruction>
<instruction note="load div html into mailheader">
<output space="store" value="msg#//m:body[@subtype='html']/text()" encoder="base64" />
<input space="store" value="mv#(//h:div)[1]" />
</instruction>
<instruction note="send message by evaluating the msg osi.">
<input space="store" value="msg" eval="true" />
</instruction>
That should work! Putting the entire Obyx control together..
<instruction xmlns="http://www.obyx.org" note="emailer">
<input>
<instruction note="load html namespace">
<output space="namespace" value="h" />
<input value="http://www.w3.org/1999/xhtml" />
</instruction>
<instruction note="load message namespace">
<output space="namespace" value="m" />
<input value="http://www.obyx.org/message" />
</instruction>
<instruction note="load html mailview">
<output space="store" value="mv" />
<input space="file" value="mymailer.html" />
</instruction>
<instruction note="load osi/message">
<output space="store" value="msg" />
<input space="file" value="mymailer.osi" />
</instruction>
<instruction operation="append" note="add style attribute with inline gif">
<output space="store" value="mv#//h:h2/h:span/@style/child-gap()" />
<input value="background:url(data:image/gif;base64," />
<input>
<mapping operation="substitute" note="strip newlines for data:">
<domain space="file" value="mymailer.gif" encoder="base64" />
<match><key format="regex" value="\r|\n" /></match>
</mapping>
</input>
<input value=") 0 50% no-repeat;" />
</instruction>
<iteration operation="sql" note="grab details and send emails out" >
<control value="select name,email,message from mailing" />
<body>
<instruction note="do message">
<output space="store" value="msg#//m:body[@subtype='plain']/text()" encoder="base64" />
<output space="store" value="mv#//h:div/h:p/text()" />
<input space="field" value="message" />
</instruction>
<instruction note="set title of markup and recipient header">
<output space="store" value="msg#//m:header[@name='To']/m:address/@note" />
<output space="store" value="mv#//h:h2/h:span/text()" />
<input space="field" value="name" />
</instruction>
<instruction note="set recipient email">
<output space="store" value="msg#//m:header[@name='To']/m:address/@value" />
<input space="field" value="email" />
</instruction>
<instruction note="load div html into mailheader">
<output space="store" value="msg#//m:body[@subtype='html']/text()" encoder="base64" />
<input space="store" value="mv#(//h:div)[1]" />
</instruction>
<instruction note="send message by evaluating the msg osi.">
<input space="store" value="msg" eval="true" />
</instruction>
</body>
</iteration>
</input>
</instruction>