JMAP in Node-RED
JMAP is a relatively obscure email protocol mainly (if not solely) used by Fastmail. I really like Fastmail because they have a lot of features and a great service but strangely they also have an API based on JMAP — which they invented. JMAP is basically IMAP+SMTP replacement but with command batching and JSON as a transfer protocol/format. Fastmail uses it in their clients, I don’t know of any third party email client that also uses it.
I came across the following resources when implementing this flow:
- GitHub repo with some examples from Fastmail
- JMAP Core (RFC 8620)
- JMAP Mail (RFC 8621)
- JMAP over WebSocket (RFC 8887)
- Jmap.io which is the organisation managing the RFCs and their GitHub
I recently also read of a mail server that also supports JMAP — Stalwart Labs so that’s something.
I will dive into describing the flow that I created to send emails via JMAP and Fastmail. I include the flow that I have created, please respect the license.
Feel free to take and extend at will, the license is simply don’t do evil.
Initialisation
In JMAP to send an Email, you first have to create a draft email. To create a draft email, you need to have a draft folder ID. To get a draft folder, you need to have the account ID of your account. The from
email address needs to have an ID. This is because you need to register the from email with Fastmail first, so you need to obtain a list of legit from emails and then have a mapping to their respective ID.
These actions are best described in the following sequence diagram.
[Edit: for sequence diagram details see original post]
These are the three steps that are done in the [fm] init
group. All this configuration is store on the msg object as msg.fastmail = { ... }
. I have kept all flows stateless, so this [fm] init
flow is repeated continuously. The flow also assumes that an FASTMAIL_API
environment variable has been set. An API key can be obtained from the user account at Fastmail.
[Edit: for flow details see original post]
But we can optimise this because JMAP allows for batching of commands, so we can combine the last two requests into a single request. The sequence diagram becomes this:
[Edit: for sequence diagram details see original post]
This improved flow is represented by the [fm] init - v2
group.
[Edit: for flow details see original post]
Sending Email
Sending email is represented by this sequence diagram
[Edit: for sequence diagram details see original post]
It basically consists of creating the email object, filling it with content, sending off a draft to Fastmail and telling Fastmail to send the email.
[Edit: for flow details see original post]
The construction and sending of the email is done complete in the single function node labelled send email
, in the group [fm] send email
.
There are two link-in nodes, one is labelled [fm] send email
and is used when Fastmail has not yet been initialised. The other is the [fm] send email (no warmup)
link node that is used when sending emails with attachment.
Sending Email with Attachment
For sending an email with an attachment, I always create a zip file from the original content. This makes the email smaller and provides the same flow for multiple files. Sending attachments with JMAP requires an extra step of uploading the contents to the Fastmail “Upload URL” server. The URL is obtained as part of the initialisation of Fastmail client.
[Edit: for sequence diagram details see original post]
The same sequence diagram becomes this Node-RED flow:
[Edit: for flow details see original post]
I can call the [fm] send email (no warmup)
node because I have already initialised Fastmail and the details are still stored in the msg
object.
Msg object contents
To send an email, the msg
must contain the email details, these are stored on the email
attribute of the msg object:
msg.email = {
/* addresses */
from: "sender@example.org",
from_name: "Example Robot",
to: "recipient@example.org",
cc: false,
bcc: false,
/* content */
subject: "[Prefix] Subject of Email",
text: "Text content can differ from html content.\n",
html: "<h1>HTML</h1> <br><b>Html</b> is a lot of fun<br>",
/* attachments */
attachments: [
{
payload: JSON.stringify(msg),
filename: "msg.json"
}
]
}
return msg;
Since this example contains an attachment, it can only be passed to the [fm] send email with attachments
link-in node. It can also be passed to the [fm] send email
link-in node but the attachment would be ignored.
JMAP — final feelings
My feelings towards JMAP are neutral, simply because I can see the advantages but I had to experience the negatives. The negatives being the lack of documentation (non-RFC documentation) and examples. The advantage is that once it has been understood, it makes sense. JMAP was not designed to send single emails, it was designed for mobile clients where internet connections can be flaky.
License and Disclaimer
Everything described here is in constant flux and improvement, the most current flow for JMAP functionality is available online. The sample given here works but no guarantee of success is given. The flow, as all flows there, can be exported and used at will under the don’t do evil license.¹
The complete Node-RED flow:
[Edit: for flow details see original post]
Hope this helps someone, thanks for getting this far!
¹: Inspiration for the license came from Douglas Crockfords’ JSLint license, further discussion at Hacker News.
Last updated: 2023–07–26T21:37:02.750Z