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:
I recently came across a mailserver that also supports JMAP: Stalwart Labs has developed one.
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.
License:Feel free to take and extend at will, the license is simply don’t do evil.
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. For sending emails, the from
email address needs to be an ID. This is because you need to register the sender email with Fastmail first, so you need to obtain a list of email addresses that have been registered with Fastmail. You then need a mapping from these emails to IDs so that you can include as email-address-ID in the draft email.
Obtaining all this prerequistes is described in the following sequence diagram.
sequenceDiagram
OMM->>Fastmail (WellKnown): Please provide Account details
Fastmail (WellKnown)->>OMM: account_id & uploadUrl for FASTMAIL_API token
OMM->>OMM: Append account Id to `msg`
OMM->>OMM: Append upload URL to `msg`
OMM->>Fastmail (API): Send Draft folder id
Fastmail (API)->>OMM: draft folder id
OMM->>OMM: Store draft folder id on `msg` object
OMM->>Fastmail (API): Provide a list of registered sender emails
Fastmail (API)->>OMM: Mapping of from email to ID
OMM->>OMM: Store mapping on `msg` object
These are the three steps that are done in the [fm] init - v1
group. All this configuration is store on the msg object as msg.fastmail = { ... }
. I have kept all flows stateless, so this [fm] init - v1
flow is used continuously. The flow assumes that an FASTMAIL_API
environment variable has been set. An API key can be obtained from the user account at Fastmail.
[
{
"id": "2baec5f863a37d04",
"type": "group",
"z": "90196166b57a77e5",
"name": "[fm] init - v1",
"style": {
"label": true
},
"nodes": [
"ec76c338b8557196",
"13730bd6088ebd5c",
"6b5447389835315d",
"8cd65ac22eed3123",
"7ffcc73c52c06f52",
"2e0d64da4844738a",
"b87b4a0317bf4065",
"be5add6bef62b488",
"2a39ebb7b2c86d94",
"74b92df3cbe129f9",
"adb3070af26d5241"
],
"x": 485,
"y": 98,
"w": 671,
"h": 413
},
{
"id": "ec76c338b8557196",
"type": "http request",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "Fastmail (WellKnown)",
"method": "GET",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 969,
"y": 261,
"wires": [
[
"13730bd6088ebd5c"
]
]
},
{
"id": "13730bd6088ebd5c",
"type": "function",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "get account_id",
"func": "msg.fastmail = {\n ...msg.fastmail,\n account: msg.payload.accounts,\n account_id: Object.keys(msg.payload.accounts)[0]\n};\n\nmsg.fastmail[\"uploadUrl\"] = msg.payload.uploadUrl.replace( \"{accountId}\", msg.fastmail.account_id );\n\nmsg.headers = {\n \"Authorization\": \"Bearer \" + env.get(\"FASTMAIL_API\")\n}\nmsg.url = \"https://api.fastmail.com/jmap/api\"\n\nmsg.payload = {\n \"using\": [\n \"urn:ietf:params:jmap:core\",\n \"urn:ietf:params:jmap:mail\"\n ],\n \"methodCalls\": [\n [\n \"Mailbox/query\",\n {\n \"accountId\": msg.fastmail.account_id,\n \"filter\": { \n \"name\": \"Drafts\" \n }\n },\n \"a\",\n ]\n ],\n };\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 720,
"y": 316,
"wires": [
[
"6b5447389835315d"
]
]
},
{
"id": "6b5447389835315d",
"type": "http request",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "Fastmail (API)",
"method": "POST",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 949,
"y": 316,
"wires": [
[
"8cd65ac22eed3123"
]
]
},
{
"id": "8cd65ac22eed3123",
"type": "function",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "get draft mailbox id",
"func": "msg.fastmail = {\n ...msg.fastmail,\n draft_mailbox_id: msg.payload[\"methodResponses\"][0][1][\"ids\"][0]\n};\n\nmsg.headers = {\n \"Authorization\": \"Bearer \" + env.get(\"FASTMAIL_API\")\n}\n\nmsg.url = \"https://api.fastmail.com/jmap/api\"\n\nmsg.payload = {\n \"using\": [\n \"urn:ietf:params:jmap:core\",\n \"urn:ietf:params:jmap:mail\",\n \"urn:ietf:params:jmap:submission\",\n ],\n \"methodCalls\": [\n [\n \"Identity/get\",\n {\n \"accountId\": msg.fastmail.account_id,\n },\n \"pluckaduck\"\n ]\n ]\n};\n\nreturn msg;\n",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 730,
"y": 377,
"wires": [
[
"7ffcc73c52c06f52"
]
]
},
{
"id": "7ffcc73c52c06f52",
"type": "http request",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "Fastmail (API)",
"method": "POST",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 949,
"y": 377,
"wires": [
[
"2e0d64da4844738a"
]
]
},
{
"id": "2e0d64da4844738a",
"type": "function",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "email aliases to ids",
"func": "var emailToId = {};\nvar lst = msg.payload[\"methodResponses\"][0][1]['list'];\n\nfor ( var idx = 0 ; idx < lst.length ; idx++ ) {\n var dt = lst[idx];\n emailToId[dt.email] = dt.id;\n}\n\nmsg.fastmail = {\n ...msg.fastmail,\n emailsToIds: emailToId\n};\n\ndelete msg.payload;\n\nreturn msg; \n",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 836.0001220703125,
"y": 441,
"wires": [
[
"adb3070af26d5241"
]
]
},
{
"id": "b87b4a0317bf4065",
"type": "function",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "init request",
"func": "msg.headers = {\n \"Authorization\": \"Bearer \" + env.get(\"FASTMAIL_API\")\n}\nmsg.url = \"https://api.fastmail.com/.well-known/jmap\"\n\nmsg.fastmail = {}\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 710,
"y": 261,
"wires": [
[
"ec76c338b8557196"
]
]
},
{
"id": "be5add6bef62b488",
"type": "catch",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "",
"scope": [
"ec76c338b8557196",
"13730bd6088ebd5c",
"6b5447389835315d",
"8cd65ac22eed3123",
"7ffcc73c52c06f52",
"2e0d64da4844738a",
"b87b4a0317bf4065"
],
"uncaught": false,
"x": 742,
"y": 139,
"wires": [
[
"2a39ebb7b2c86d94"
]
]
},
{
"id": "2a39ebb7b2c86d94",
"type": "debug",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "debug 14",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 946,
"y": 140,
"wires": []
},
{
"id": "74b92df3cbe129f9",
"type": "link in",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "[fm] init",
"links": [],
"x": 526,
"y": 222,
"wires": [
[
"b87b4a0317bf4065"
]
]
},
{
"id": "adb3070af26d5241",
"type": "link out",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "link out 71",
"mode": "return",
"links": [],
"x": 1115,
"y": 470,
"wires": []
}
]
We can optimise this flow because JMAP allows for batching of commands, so we can combine the last two requests into a single request. The last two steps are two commands however they can be sent together, with Fastmail sending one response with the results of both commands. The sequence diagram then becomes this:
sequenceDiagram
OMM->>Fastmail (WellKnown): Please provide Account details
Fastmail (WellKnown)->>OMM: account_id for FASTMAIL_API token
OMM->>OMM: Append account Id to `msg`
OMM->>OMM: upload URL to `msg`
OMM->>Fastmail (API): Send Draft folder id & list of registered sender emails
Fastmail (API)->>OMM: draft folder id & registered emails
OMM->>OMM: Store draft folder id on `msg` object
OMM->>OMM: Store sender email mapping on `msg` object
This improved flow is represented by the [fm] init - v2
group.
[
{
"id": "1dc237a9d33d5ed3",
"type": "group",
"z": "90196166b57a77e5",
"name": "[fm] init - v2",
"style": {
"label": true
},
"nodes": [
"7d74ccf860684abe",
"95a22bf5e1205c02",
"efac3587264d74a5",
"bbd58f295c0e18cf",
"5d1059e533c2d586",
"dc70ab87b7721ecc",
"93b5e2879c7e0daf",
"b7a122e21273141b",
"739fdbcd8491e989"
],
"x": 1469,
"y": 91,
"w": 752,
"h": 461
},
{
"id": "7d74ccf860684abe",
"type": "http request",
"z": "90196166b57a77e5",
"g": "1dc237a9d33d5ed3",
"name": "Fastmail (WellKnown)",
"method": "GET",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 1976,
"y": 254,
"wires": [
[
"95a22bf5e1205c02"
]
]
},
{
"id": "95a22bf5e1205c02",
"type": "function",
"z": "90196166b57a77e5",
"g": "1dc237a9d33d5ed3",
"name": "store acount_id and upload URL and \\n prepare request draft and emails",
"func": "msg.fastmail = {\n ...msg.fastmail,\n account: msg.payload.accounts,\n account_id: Object.keys(msg.payload.accounts)[0]\n};\n\nmsg.fastmail[\"uploadUrl\"] = msg.payload.uploadUrl.replace( \"{accountId}\", msg.fastmail.account_id );\n\nmsg.headers = {\n \"Authorization\": \"Bearer \" + env.get(\"FASTMAIL_API\")\n}\n\nmsg.url = \"https://api.fastmail.com/jmap/api\"\n\nmsg.payload = {\n \"using\": [\n \"urn:ietf:params:jmap:core\",\n \"urn:ietf:params:jmap:mail\",\n \"urn:ietf:params:jmap:submission\",\n ],\n \"methodCalls\": [\n [\n \"Mailbox/query\",\n {\n \"accountId\": msg.fastmail.account_id,\n \"filter\": { \n \"name\": \"Drafts\" \n }\n },\n \"a\",\n ],\n [\n \"Identity/get\",\n {\n \"accountId\": msg.fastmail.account_id,\n },\n \"pluckaduck\"\n ]\n ],\n };\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1686,
"y": 344,
"wires": [
[
"efac3587264d74a5"
]
]
},
{
"id": "efac3587264d74a5",
"type": "http request",
"z": "90196166b57a77e5",
"g": "1dc237a9d33d5ed3",
"name": "Fastmail (API)",
"method": "POST",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 1986,
"y": 344,
"wires": [
[
"bbd58f295c0e18cf"
]
]
},
{
"id": "bbd58f295c0e18cf",
"type": "function",
"z": "90196166b57a77e5",
"g": "1dc237a9d33d5ed3",
"name": "store draft id and email2ids list",
"func": "msg.fastmail = {\n ...msg.fastmail,\n draft_mailbox_id: msg.payload[\"methodResponses\"][0][1][\"ids\"][0]\n};\n\nvar emailToId = {};\nvar lst = msg.payload[\"methodResponses\"][1][1]['list'];\n\nfor ( var idx = 0 ; idx < lst.length ; idx++ ) {\n var dt = lst[idx];\n emailToId[dt.email] = dt.id;\n}\n\nmsg.fastmail = {\n ...msg.fastmail,\n emailsToIds: emailToId\n};\n\ndelete msg.payload;\n\nreturn msg; \n",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1709.0001220703125,
"y": 435,
"wires": [
[
"739fdbcd8491e989"
]
]
},
{
"id": "5d1059e533c2d586",
"type": "function",
"z": "90196166b57a77e5",
"g": "1dc237a9d33d5ed3",
"name": "init request",
"func": "msg.headers = {\n \"Authorization\": \"Bearer \" + env.get(\"FASTMAIL_API\")\n}\nmsg.url = \"https://api.fastmail.com/.well-known/jmap\"\n\nmsg.fastmail = {}\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1717,
"y": 254,
"wires": [
[
"7d74ccf860684abe"
]
]
},
{
"id": "dc70ab87b7721ecc",
"type": "catch",
"z": "90196166b57a77e5",
"g": "1dc237a9d33d5ed3",
"name": "",
"scope": [
"7d74ccf860684abe",
"95a22bf5e1205c02",
"efac3587264d74a5",
"bbd58f295c0e18cf",
"5d1059e533c2d586"
],
"uncaught": false,
"x": 1749,
"y": 132,
"wires": [
[
"93b5e2879c7e0daf"
]
]
},
{
"id": "93b5e2879c7e0daf",
"type": "debug",
"z": "90196166b57a77e5",
"g": "1dc237a9d33d5ed3",
"name": "debug 34",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1953,
"y": 133,
"wires": []
},
{
"id": "b7a122e21273141b",
"type": "link in",
"z": "90196166b57a77e5",
"g": "1dc237a9d33d5ed3",
"name": "[fm] init - v2",
"links": [],
"x": 1510,
"y": 185,
"wires": [
[
"5d1059e533c2d586"
]
]
},
{
"id": "739fdbcd8491e989",
"type": "link out",
"z": "90196166b57a77e5",
"g": "1dc237a9d33d5ed3",
"name": "link out 105",
"mode": "return",
"links": [],
"x": 2180,
"y": 511,
"wires": []
}
]
Now we can begin to send emails, the next flow shows how that is done.
The sequence diagram for sending email:
sequenceDiagram
OMM->>OMM: Initialise Fastmail
OMM->>OMM: Construct email object
OMM->>Fastmail (API): Create draft email in draft folder & send email from there
Fastmail (API)->>OMM: email sent
The sequence consists of obtaining initial data from Fastmail, creating the email object, filling it with content. The email object created for creating the draft contains the from, to, text content and html content of the email, the details required for an email. The sequence continues by creating a draft email and immediately sending the email. Those two separate commands that Fastmail executes.
The Node-RED flow that I created for that:
[
{
"id": "913082e616d84dfa",
"type": "group",
"z": "90196166b57a77e5",
"name": "[fm] send email",
"style": {
"label": true
},
"nodes": [
"78152de1e45c8c18",
"84adc7ab61115326",
"7f5f8e8f9041be65",
"414a7c88302f52db",
"093e9a4fc6067fbe",
"858a5a9948ca5ce8",
"ce0e6936b12cc43b",
"106fe2967e41dced"
],
"x": 485,
"y": 665,
"w": 531,
"h": 372
},
{
"id": "78152de1e45c8c18",
"type": "function",
"z": "90196166b57a77e5",
"g": "913082e616d84dfa",
"name": "send email",
"func": "var to_email = msg.email.to;\nvar cc_email = msg.email.cc;\nvar from_email = msg.email.from || env.get(\"SNDML_FROM_EMAIL\");\nvar bcc_email = msg.email.bcc || env.get(\"SNDML_BCC_EMAIL\");\n\nif ( !from_email ) {\n throw \"Missing from email when sending email\";\n}\n\nvar draft_id = \"draft_\" + RED.util.generateId();\n\nvar draft = {\n \"from\": [\n { \n \"email\": from_email, \n \"name\": msg.email.from_name || from_email\n }\n ],\n \"to\": [\n { \n \"email\": to_email \n }\n ],\n \"subject\": msg.email.subject,\n \"keywords\": { \"$draft\": true },\n \"mailboxIds\": { },\n \"bodyValues\": {\n \"body\": { \"value\": msg.email.text, \"charset\": \"utf-8\" },\n \"bodyHTML\": { \"value\": msg.email.html, \"charset\": \"utf-8\" }\n },\n \"textBody\": [{ \"partId\": \"body\", \"type\": \"text/plain\" }],\n \"htmlBody\": [{ \"partId\": \"bodyHTML\", \"type\": \"text/html\" }],\n};\n\nvar rcptTo = [\n {\n \"email\": to_email,\n \"parameters\": null\n }\n];\n\nif ( bcc_email && (msg.email.bcc != false)) {\n draft[\"bcc\"] = [{ \"email\": bcc_email }];\n rcptTo.push({\n \"email\": bcc_email,\n \"parameters\": \"bcc\"\n });\n}\n\nif (cc_email && (msg.email.cc != false)) {\n draft[\"cc\"] = [{ \"email\": cc_email }];\n rcptTo.push({\n \"email\": cc_email,\n \"parameters\": \"cc\"\n });\n}\n\nif (msg.email.attachments) {\n draft[\"attachments\"] = [];\n msg.email.attachments.forEach(function(att){\n draft[\"attachments\"].push({\n \"blobId\": att.blobId,\n \"type\": att.type,\n \"name\": att.filename\n });\n });\n}\n\ndraft[\"mailboxIds\"][msg.fastmail.draft_mailbox_id] = true;\n\nvar drafty = {};\ndrafty[draft_id] = draft;\n\nmsg.payload = {\n \"using\": [\n \"urn:ietf:params:jmap:core\",\n \"urn:ietf:params:jmap:mail\",\n \"urn:ietf:params:jmap:submission\",\n ],\n \"methodCalls\": [\n [\n \"Email/set\",\n {\n \"accountId\": msg.fastmail.account_id,\n \"create\": drafty\n },\n \"a\"\n ],\n [\n \"EmailSubmission/set\",\n {\n \"accountId\": msg.fastmail.account_id,\n \"onSuccessDestroyEmail\": [\"#sendIt\"],\n \"create\": {\n \"sendIt\": {\n \"identityId\": msg.fastmail.emailsToIds[from_email],\n \"emailId\": \"#\" + draft_id,\n \"envelope\": {\n \"mailFrom\": {\n \"email\": from_email,\n \"parameters\": null\n },\n \"rcptTo\": rcptTo\n },\n }\n },\n },\n \"b\",\n ],\n ],\n}\n\nmsg.headers = {\n \"Authorization\": \"Bearer \" + env.get(\"FASTMAIL_API\")\n}\n\nmsg.url = \"https://api.fastmail.com/jmap/api\"\n\nreturn msg;\n",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 736,
"y": 876,
"wires": [
[
"7f5f8e8f9041be65"
]
]
},
{
"id": "84adc7ab61115326",
"type": "link call",
"z": "90196166b57a77e5",
"g": "913082e616d84dfa",
"name": "[fm] init - v2",
"links": [
"b7a122e21273141b"
],
"linkType": "static",
"timeout": "30",
"x": 739,
"y": 795,
"wires": [
[
"78152de1e45c8c18"
]
]
},
{
"id": "7f5f8e8f9041be65",
"type": "http request",
"z": "90196166b57a77e5",
"g": "913082e616d84dfa",
"name": "Fastmail (API)",
"method": "POST",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 759,
"y": 970,
"wires": [
[
"093e9a4fc6067fbe"
]
]
},
{
"id": "414a7c88302f52db",
"type": "link in",
"z": "90196166b57a77e5",
"g": "913082e616d84dfa",
"name": "[fm] send email",
"links": [],
"x": 526,
"y": 763,
"wires": [
[
"84adc7ab61115326"
]
]
},
{
"id": "093e9a4fc6067fbe",
"type": "link out",
"z": "90196166b57a77e5",
"g": "913082e616d84dfa",
"name": "link out 96",
"mode": "return",
"links": [],
"x": 975,
"y": 996,
"wires": []
},
{
"id": "858a5a9948ca5ce8",
"type": "catch",
"z": "90196166b57a77e5",
"g": "913082e616d84dfa",
"name": "",
"scope": [
"78152de1e45c8c18",
"7f5f8e8f9041be65"
],
"uncaught": false,
"x": 647,
"y": 706,
"wires": [
[
"ce0e6936b12cc43b"
]
]
},
{
"id": "ce0e6936b12cc43b",
"type": "debug",
"z": "90196166b57a77e5",
"g": "913082e616d84dfa",
"name": "debug 30",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 818,
"y": 706,
"wires": []
},
{
"id": "106fe2967e41dced",
"type": "link in",
"z": "90196166b57a77e5",
"g": "913082e616d84dfa",
"name": "[fm] send email (no warmup)",
"links": [],
"x": 526,
"y": 830,
"wires": [
[
"78152de1e45c8c18"
]
]
}
]
The construction and sending of the email is done complete in the single function node labelled send email
. The code for the node shows the construction of the email object.
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.
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.
sequenceDiagram
OMM->>OMM: [fm] init - v2
OMM->>OMM: Construct zip containing original payload
OMM->>Fastmail (UploadURL): Upload Zip and obtain blobId
Fastmail (UploadURL)->>OMM: blobId of Zip file
OMM->>OMM: Create email including attachment details
OMM->>OMM: [fm] send email (no warmup)
The same sequence diagram becomes this Node-RED flow:
[
{
"id": "e6f09bcaca0642ce",
"type": "group",
"z": "90196166b57a77e5",
"name": "[fm] send email with attachments",
"style": {
"label": true
},
"nodes": [
"45ec84e93e723b98",
"f20245aba2053f83",
"9fe5b2e2ec45b3ef",
"f04c04f476e9b9c6",
"5202b587b5556804",
"e1e9182cbbbb3eb9",
"3be4415fdf05acaf",
"0cea4b8a0b7b0cdc",
"59aaf14c3a34ee6f",
"a54744c362f980f7",
"90942be6906dc62e"
],
"x": 1148,
"y": 660,
"w": 1081,
"h": 281
},
{
"id": "45ec84e93e723b98",
"type": "function",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "create email content",
"func": "if ( msg.statusCode == 200 ) {\n msg.email.attachments = [\n {\n \"filename\": \"attachment.zip\",\n \"blobId\": msg.payload.blobId,\n \"type\": msg.payload.type\n }\n ]\n return msg;\n}",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1930,
"y": 844,
"wires": [
[
"59aaf14c3a34ee6f"
]
]
},
{
"id": "f20245aba2053f83",
"type": "link call",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "[fm] init - v2",
"links": [
"b7a122e21273141b"
],
"linkType": "static",
"timeout": "30",
"x": 1281,
"y": 899,
"wires": [
[
"9fe5b2e2ec45b3ef"
]
]
},
{
"id": "9fe5b2e2ec45b3ef",
"type": "change",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "email.attachments",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 1456,
"y": 701,
"wires": [
[
"f04c04f476e9b9c6"
]
]
},
{
"id": "f04c04f476e9b9c6",
"type": "zip",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "",
"mode": "compress",
"filename": "attachment.zip",
"compressionlevel": "9",
"outasstring": false,
"x": 1681,
"y": 701,
"wires": [
[
"3be4415fdf05acaf"
]
]
},
{
"id": "5202b587b5556804",
"type": "link in",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "[fm] send email with attachments",
"links": [],
"x": 1189,
"y": 707,
"wires": [
[
"f20245aba2053f83"
]
]
},
{
"id": "e1e9182cbbbb3eb9",
"type": "link out",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "link out 94",
"mode": "return",
"links": [],
"x": 2188,
"y": 900,
"wires": []
},
{
"id": "3be4415fdf05acaf",
"type": "change",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "",
"rules": [
{
"t": "set",
"p": "url",
"pt": "msg",
"to": "fastmail.uploadUrl",
"tot": "msg"
},
{
"t": "set",
"p": "headers",
"pt": "msg",
"to": "{\t \"Authorization\": \"Bearer \" & $env(\"FASTMAIL_API\")\t}",
"tot": "jsonata"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 1915,
"y": 701,
"wires": [
[
"0cea4b8a0b7b0cdc"
]
]
},
{
"id": "0cea4b8a0b7b0cdc",
"type": "http request",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "Fastmail (uploadURL)",
"method": "POST",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 1923,
"y": 775,
"wires": [
[
"45ec84e93e723b98"
]
]
},
{
"id": "59aaf14c3a34ee6f",
"type": "link call",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "[fm] send email (no warmup)",
"links": [
"106fe2967e41dced"
],
"linkType": "static",
"timeout": "30",
"x": 1979,
"y": 900,
"wires": [
[
"e1e9182cbbbb3eb9"
]
]
},
{
"id": "a54744c362f980f7",
"type": "catch",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "",
"scope": [
"9fe5b2e2ec45b3ef",
"f04c04f476e9b9c6",
"3be4415fdf05acaf",
"0cea4b8a0b7b0cdc",
"45ec84e93e723b98"
],
"uncaught": false,
"x": 1573,
"y": 808,
"wires": [
[
"90942be6906dc62e"
]
]
},
{
"id": "90942be6906dc62e",
"type": "debug",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "debug 31",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1692,
"y": 899,
"wires": []
}
]
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.
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.
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.
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.[1]
The complete Node-RED flow:
[
{
"id": "2baec5f863a37d04",
"type": "group",
"z": "90196166b57a77e5",
"name": "[fm] init - v1",
"style": {
"label": true
},
"nodes": [
"ec76c338b8557196",
"13730bd6088ebd5c",
"6b5447389835315d",
"8cd65ac22eed3123",
"7ffcc73c52c06f52",
"2e0d64da4844738a",
"b87b4a0317bf4065",
"be5add6bef62b488",
"2a39ebb7b2c86d94",
"74b92df3cbe129f9",
"adb3070af26d5241"
],
"x": 485,
"y": 98,
"w": 671,
"h": 413
},
{
"id": "ec76c338b8557196",
"type": "http request",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "Fastmail (WellKnown)",
"method": "GET",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 969,
"y": 261,
"wires": [
[
"13730bd6088ebd5c"
]
]
},
{
"id": "13730bd6088ebd5c",
"type": "function",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "get account_id",
"func": "msg.fastmail = {\n ...msg.fastmail,\n account: msg.payload.accounts,\n account_id: Object.keys(msg.payload.accounts)[0]\n};\n\nmsg.fastmail[\"uploadUrl\"] = msg.payload.uploadUrl.replace( \"{accountId}\", msg.fastmail.account_id );\n\nmsg.headers = {\n \"Authorization\": \"Bearer \" + env.get(\"FASTMAIL_API\")\n}\nmsg.url = \"https://api.fastmail.com/jmap/api\"\n\nmsg.payload = {\n \"using\": [\n \"urn:ietf:params:jmap:core\",\n \"urn:ietf:params:jmap:mail\"\n ],\n \"methodCalls\": [\n [\n \"Mailbox/query\",\n {\n \"accountId\": msg.fastmail.account_id,\n \"filter\": { \n \"name\": \"Drafts\" \n }\n },\n \"a\",\n ]\n ],\n };\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 720,
"y": 316,
"wires": [
[
"6b5447389835315d"
]
]
},
{
"id": "6b5447389835315d",
"type": "http request",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "Fastmail (API)",
"method": "POST",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 949,
"y": 316,
"wires": [
[
"8cd65ac22eed3123"
]
]
},
{
"id": "8cd65ac22eed3123",
"type": "function",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "get draft mailbox id",
"func": "msg.fastmail = {\n ...msg.fastmail,\n draft_mailbox_id: msg.payload[\"methodResponses\"][0][1][\"ids\"][0]\n};\n\nmsg.headers = {\n \"Authorization\": \"Bearer \" + env.get(\"FASTMAIL_API\")\n}\n\nmsg.url = \"https://api.fastmail.com/jmap/api\"\n\nmsg.payload = {\n \"using\": [\n \"urn:ietf:params:jmap:core\",\n \"urn:ietf:params:jmap:mail\",\n \"urn:ietf:params:jmap:submission\",\n ],\n \"methodCalls\": [\n [\n \"Identity/get\",\n {\n \"accountId\": msg.fastmail.account_id,\n },\n \"pluckaduck\"\n ]\n ]\n};\n\nreturn msg;\n",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 730,
"y": 377,
"wires": [
[
"7ffcc73c52c06f52"
]
]
},
{
"id": "7ffcc73c52c06f52",
"type": "http request",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "Fastmail (API)",
"method": "POST",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 949,
"y": 377,
"wires": [
[
"2e0d64da4844738a"
]
]
},
{
"id": "2e0d64da4844738a",
"type": "function",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "email aliases to ids",
"func": "var emailToId = {};\nvar lst = msg.payload[\"methodResponses\"][0][1]['list'];\n\nfor ( var idx = 0 ; idx < lst.length ; idx++ ) {\n var dt = lst[idx];\n emailToId[dt.email] = dt.id;\n}\n\nmsg.fastmail = {\n ...msg.fastmail,\n emailsToIds: emailToId\n};\n\ndelete msg.payload;\n\nreturn msg; \n",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 836.0001220703125,
"y": 441,
"wires": [
[
"adb3070af26d5241"
]
]
},
{
"id": "b87b4a0317bf4065",
"type": "function",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "init request",
"func": "msg.headers = {\n \"Authorization\": \"Bearer \" + env.get(\"FASTMAIL_API\")\n}\nmsg.url = \"https://api.fastmail.com/.well-known/jmap\"\n\nmsg.fastmail = {}\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 710,
"y": 261,
"wires": [
[
"ec76c338b8557196"
]
]
},
{
"id": "be5add6bef62b488",
"type": "catch",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "",
"scope": [
"ec76c338b8557196",
"13730bd6088ebd5c",
"6b5447389835315d",
"8cd65ac22eed3123",
"7ffcc73c52c06f52",
"2e0d64da4844738a",
"b87b4a0317bf4065"
],
"uncaught": false,
"x": 742,
"y": 139,
"wires": [
[
"2a39ebb7b2c86d94"
]
]
},
{
"id": "2a39ebb7b2c86d94",
"type": "debug",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "debug 14",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 946,
"y": 140,
"wires": []
},
{
"id": "74b92df3cbe129f9",
"type": "link in",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "[fm] init",
"links": [],
"x": 526,
"y": 222,
"wires": [
[
"b87b4a0317bf4065"
]
]
},
{
"id": "adb3070af26d5241",
"type": "link out",
"z": "90196166b57a77e5",
"g": "2baec5f863a37d04",
"name": "link out 71",
"mode": "return",
"links": [],
"x": 1115,
"y": 470,
"wires": []
},
{
"id": "913082e616d84dfa",
"type": "group",
"z": "90196166b57a77e5",
"name": "[fm] send email",
"style": {
"label": true
},
"nodes": [
"78152de1e45c8c18",
"84adc7ab61115326",
"7f5f8e8f9041be65",
"414a7c88302f52db",
"093e9a4fc6067fbe",
"858a5a9948ca5ce8",
"ce0e6936b12cc43b",
"106fe2967e41dced"
],
"x": 485,
"y": 665,
"w": 531,
"h": 372
},
{
"id": "78152de1e45c8c18",
"type": "function",
"z": "90196166b57a77e5",
"g": "913082e616d84dfa",
"name": "send email",
"func": "var to_email = msg.email.to;\nvar cc_email = msg.email.cc;\nvar from_email = msg.email.from || env.get(\"SNDML_FROM_EMAIL\");\nvar bcc_email = msg.email.bcc || env.get(\"SNDML_BCC_EMAIL\");\n\nif ( !from_email ) {\n throw \"Missing from email when sending email\";\n}\n\nvar draft_id = \"draft_\" + RED.util.generateId();\n\nvar draft = {\n \"from\": [\n { \n \"email\": from_email, \n \"name\": msg.email.from_name || from_email\n }\n ],\n \"to\": [\n { \n \"email\": to_email \n }\n ],\n \"subject\": msg.email.subject,\n \"keywords\": { \"$draft\": true },\n \"mailboxIds\": { },\n \"bodyValues\": {\n \"body\": { \"value\": msg.email.text, \"charset\": \"utf-8\" },\n \"bodyHTML\": { \"value\": msg.email.html, \"charset\": \"utf-8\" }\n },\n \"textBody\": [{ \"partId\": \"body\", \"type\": \"text/plain\" }],\n \"htmlBody\": [{ \"partId\": \"bodyHTML\", \"type\": \"text/html\" }],\n};\n\nvar rcptTo = [\n {\n \"email\": to_email,\n \"parameters\": null\n }\n];\n\nif ( bcc_email && (msg.email.bcc != false)) {\n draft[\"bcc\"] = [{ \"email\": bcc_email }];\n rcptTo.push({\n \"email\": bcc_email,\n \"parameters\": \"bcc\"\n });\n}\n\nif (cc_email && (msg.email.cc != false)) {\n draft[\"cc\"] = [{ \"email\": cc_email }];\n rcptTo.push({\n \"email\": cc_email,\n \"parameters\": \"cc\"\n });\n}\n\nif (msg.email.attachments) {\n draft[\"attachments\"] = [];\n msg.email.attachments.forEach(function(att){\n draft[\"attachments\"].push({\n \"blobId\": att.blobId,\n \"type\": att.type,\n \"name\": att.filename\n });\n });\n}\n\ndraft[\"mailboxIds\"][msg.fastmail.draft_mailbox_id] = true;\n\nvar drafty = {};\ndrafty[draft_id] = draft;\n\nmsg.payload = {\n \"using\": [\n \"urn:ietf:params:jmap:core\",\n \"urn:ietf:params:jmap:mail\",\n \"urn:ietf:params:jmap:submission\",\n ],\n \"methodCalls\": [\n [\n \"Email/set\",\n {\n \"accountId\": msg.fastmail.account_id,\n \"create\": drafty\n },\n \"a\"\n ],\n [\n \"EmailSubmission/set\",\n {\n \"accountId\": msg.fastmail.account_id,\n \"onSuccessDestroyEmail\": [\"#sendIt\"],\n \"create\": {\n \"sendIt\": {\n \"identityId\": msg.fastmail.emailsToIds[from_email],\n \"emailId\": \"#\" + draft_id,\n \"envelope\": {\n \"mailFrom\": {\n \"email\": from_email,\n \"parameters\": null\n },\n \"rcptTo\": rcptTo\n },\n }\n },\n },\n \"b\",\n ],\n ],\n}\n\nmsg.headers = {\n \"Authorization\": \"Bearer \" + env.get(\"FASTMAIL_API\")\n}\n\nmsg.url = \"https://api.fastmail.com/jmap/api\"\n\nreturn msg;\n",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 736,
"y": 876,
"wires": [
[
"7f5f8e8f9041be65"
]
]
},
{
"id": "84adc7ab61115326",
"type": "link call",
"z": "90196166b57a77e5",
"g": "913082e616d84dfa",
"name": "",
"links": [
"b7a122e21273141b"
],
"linkType": "static",
"timeout": "30",
"x": 739,
"y": 795,
"wires": [
[
"78152de1e45c8c18"
]
]
},
{
"id": "7f5f8e8f9041be65",
"type": "http request",
"z": "90196166b57a77e5",
"g": "913082e616d84dfa",
"name": "Fastmail (API)",
"method": "POST",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 759,
"y": 970,
"wires": [
[
"093e9a4fc6067fbe"
]
]
},
{
"id": "414a7c88302f52db",
"type": "link in",
"z": "90196166b57a77e5",
"g": "913082e616d84dfa",
"name": "[fm] send email",
"links": [],
"x": 526,
"y": 763,
"wires": [
[
"84adc7ab61115326"
]
]
},
{
"id": "093e9a4fc6067fbe",
"type": "link out",
"z": "90196166b57a77e5",
"g": "913082e616d84dfa",
"name": "link out 96",
"mode": "return",
"links": [],
"x": 975,
"y": 996,
"wires": []
},
{
"id": "858a5a9948ca5ce8",
"type": "catch",
"z": "90196166b57a77e5",
"g": "913082e616d84dfa",
"name": "",
"scope": [
"78152de1e45c8c18",
"7f5f8e8f9041be65"
],
"uncaught": false,
"x": 647,
"y": 706,
"wires": [
[
"ce0e6936b12cc43b"
]
]
},
{
"id": "ce0e6936b12cc43b",
"type": "debug",
"z": "90196166b57a77e5",
"g": "913082e616d84dfa",
"name": "debug 30",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 818,
"y": 706,
"wires": []
},
{
"id": "106fe2967e41dced",
"type": "link in",
"z": "90196166b57a77e5",
"g": "913082e616d84dfa",
"name": "[fm] send email (no warmup)",
"links": [],
"x": 526,
"y": 830,
"wires": [
[
"78152de1e45c8c18"
]
]
},
{
"id": "e6f09bcaca0642ce",
"type": "group",
"z": "90196166b57a77e5",
"name": "[fm] send email with attachments",
"style": {
"label": true
},
"nodes": [
"45ec84e93e723b98",
"f20245aba2053f83",
"9fe5b2e2ec45b3ef",
"f04c04f476e9b9c6",
"5202b587b5556804",
"e1e9182cbbbb3eb9",
"3be4415fdf05acaf",
"0cea4b8a0b7b0cdc",
"59aaf14c3a34ee6f",
"a54744c362f980f7",
"90942be6906dc62e"
],
"x": 1148,
"y": 660,
"w": 1081,
"h": 281
},
{
"id": "45ec84e93e723b98",
"type": "function",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "create email content",
"func": "if ( msg.statusCode == 200 ) {\n msg.email.attachments = [\n {\n \"filename\": \"attachment.zip\",\n \"blobId\": msg.payload.blobId,\n \"type\": msg.payload.type\n }\n ]\n return msg;\n}",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1930,
"y": 844,
"wires": [
[
"59aaf14c3a34ee6f"
]
]
},
{
"id": "f20245aba2053f83",
"type": "link call",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "",
"links": [
"b7a122e21273141b"
],
"linkType": "static",
"timeout": "30",
"x": 1281,
"y": 899,
"wires": [
[
"9fe5b2e2ec45b3ef"
]
]
},
{
"id": "9fe5b2e2ec45b3ef",
"type": "change",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "email.attachments",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 1456,
"y": 701,
"wires": [
[
"f04c04f476e9b9c6"
]
]
},
{
"id": "f04c04f476e9b9c6",
"type": "zip",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "",
"mode": "compress",
"filename": "attachment.zip",
"compressionlevel": "9",
"outasstring": false,
"x": 1681,
"y": 701,
"wires": [
[
"3be4415fdf05acaf"
]
]
},
{
"id": "5202b587b5556804",
"type": "link in",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "[fm] send email with attachments",
"links": [],
"x": 1189,
"y": 707,
"wires": [
[
"f20245aba2053f83"
]
]
},
{
"id": "e1e9182cbbbb3eb9",
"type": "link out",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "link out 94",
"mode": "return",
"links": [],
"x": 2188,
"y": 900,
"wires": []
},
{
"id": "3be4415fdf05acaf",
"type": "change",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "",
"rules": [
{
"t": "set",
"p": "url",
"pt": "msg",
"to": "fastmail.uploadUrl",
"tot": "msg"
},
{
"t": "set",
"p": "headers",
"pt": "msg",
"to": "{\t \"Authorization\": \"Bearer \" & $env(\"FASTMAIL_API\")\t}",
"tot": "jsonata"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 1915,
"y": 701,
"wires": [
[
"0cea4b8a0b7b0cdc"
]
]
},
{
"id": "0cea4b8a0b7b0cdc",
"type": "http request",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "Fastmail (uploadURL)",
"method": "POST",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 1923,
"y": 775,
"wires": [
[
"45ec84e93e723b98"
]
]
},
{
"id": "59aaf14c3a34ee6f",
"type": "link call",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "",
"links": [
"106fe2967e41dced"
],
"linkType": "static",
"timeout": "30",
"x": 1979,
"y": 900,
"wires": [
[
"e1e9182cbbbb3eb9"
]
]
},
{
"id": "a54744c362f980f7",
"type": "catch",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "",
"scope": [
"9fe5b2e2ec45b3ef",
"f04c04f476e9b9c6",
"3be4415fdf05acaf",
"0cea4b8a0b7b0cdc",
"45ec84e93e723b98"
],
"uncaught": false,
"x": 1573,
"y": 808,
"wires": [
[
"90942be6906dc62e"
]
]
},
{
"id": "90942be6906dc62e",
"type": "debug",
"z": "90196166b57a77e5",
"g": "e6f09bcaca0642ce",
"name": "debug 31",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1692,
"y": 899,
"wires": []
},
{
"id": "1dc237a9d33d5ed3",
"type": "group",
"z": "90196166b57a77e5",
"name": "[fm] init - v2",
"style": {
"label": true
},
"nodes": [
"7d74ccf860684abe",
"95a22bf5e1205c02",
"efac3587264d74a5",
"bbd58f295c0e18cf",
"5d1059e533c2d586",
"dc70ab87b7721ecc",
"93b5e2879c7e0daf",
"b7a122e21273141b",
"739fdbcd8491e989"
],
"x": 1469,
"y": 91,
"w": 752,
"h": 461
},
{
"id": "7d74ccf860684abe",
"type": "http request",
"z": "90196166b57a77e5",
"g": "1dc237a9d33d5ed3",
"name": "Fastmail (WellKnown)",
"method": "GET",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 1976,
"y": 254,
"wires": [
[
"95a22bf5e1205c02"
]
]
},
{
"id": "95a22bf5e1205c02",
"type": "function",
"z": "90196166b57a77e5",
"g": "1dc237a9d33d5ed3",
"name": "store acount_id and upload URL and \\n prepare request draft and emails",
"func": "msg.fastmail = {\n ...msg.fastmail,\n account: msg.payload.accounts,\n account_id: Object.keys(msg.payload.accounts)[0]\n};\n\nmsg.fastmail[\"uploadUrl\"] = msg.payload.uploadUrl.replace( \"{accountId}\", msg.fastmail.account_id );\n\nmsg.headers = {\n \"Authorization\": \"Bearer \" + env.get(\"FASTMAIL_API\")\n}\n\nmsg.url = \"https://api.fastmail.com/jmap/api\"\n\nmsg.payload = {\n \"using\": [\n \"urn:ietf:params:jmap:core\",\n \"urn:ietf:params:jmap:mail\",\n \"urn:ietf:params:jmap:submission\",\n ],\n \"methodCalls\": [\n [\n \"Mailbox/query\",\n {\n \"accountId\": msg.fastmail.account_id,\n \"filter\": { \n \"name\": \"Drafts\" \n }\n },\n \"a\",\n ],\n [\n \"Identity/get\",\n {\n \"accountId\": msg.fastmail.account_id,\n },\n \"pluckaduck\"\n ]\n ],\n };\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1686,
"y": 344,
"wires": [
[
"efac3587264d74a5"
]
]
},
{
"id": "efac3587264d74a5",
"type": "http request",
"z": "90196166b57a77e5",
"g": "1dc237a9d33d5ed3",
"name": "Fastmail (API)",
"method": "POST",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 1986,
"y": 344,
"wires": [
[
"bbd58f295c0e18cf"
]
]
},
{
"id": "bbd58f295c0e18cf",
"type": "function",
"z": "90196166b57a77e5",
"g": "1dc237a9d33d5ed3",
"name": "store draft id and email2ids list",
"func": "msg.fastmail = {\n ...msg.fastmail,\n draft_mailbox_id: msg.payload[\"methodResponses\"][0][1][\"ids\"][0]\n};\n\nvar emailToId = {};\nvar lst = msg.payload[\"methodResponses\"][1][1]['list'];\n\nfor ( var idx = 0 ; idx < lst.length ; idx++ ) {\n var dt = lst[idx];\n emailToId[dt.email] = dt.id;\n}\n\nmsg.fastmail = {\n ...msg.fastmail,\n emailsToIds: emailToId\n};\n\ndelete msg.payload;\n\nreturn msg; \n",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1709.0001220703125,
"y": 435,
"wires": [
[
"739fdbcd8491e989"
]
]
},
{
"id": "5d1059e533c2d586",
"type": "function",
"z": "90196166b57a77e5",
"g": "1dc237a9d33d5ed3",
"name": "init request",
"func": "msg.headers = {\n \"Authorization\": \"Bearer \" + env.get(\"FASTMAIL_API\")\n}\nmsg.url = \"https://api.fastmail.com/.well-known/jmap\"\n\nmsg.fastmail = {}\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1717,
"y": 254,
"wires": [
[
"7d74ccf860684abe"
]
]
},
{
"id": "dc70ab87b7721ecc",
"type": "catch",
"z": "90196166b57a77e5",
"g": "1dc237a9d33d5ed3",
"name": "",
"scope": [
"7d74ccf860684abe",
"95a22bf5e1205c02",
"efac3587264d74a5",
"bbd58f295c0e18cf",
"5d1059e533c2d586"
],
"uncaught": false,
"x": 1749,
"y": 132,
"wires": [
[
"93b5e2879c7e0daf"
]
]
},
{
"id": "93b5e2879c7e0daf",
"type": "debug",
"z": "90196166b57a77e5",
"g": "1dc237a9d33d5ed3",
"name": "debug 34",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1953,
"y": 133,
"wires": []
},
{
"id": "b7a122e21273141b",
"type": "link in",
"z": "90196166b57a77e5",
"g": "1dc237a9d33d5ed3",
"name": "[fm] init - v2",
"links": [],
"x": 1510,
"y": 185,
"wires": [
[
"5d1059e533c2d586"
]
]
},
{
"id": "739fdbcd8491e989",
"type": "link out",
"z": "90196166b57a77e5",
"g": "1dc237a9d33d5ed3",
"name": "link out 105",
"mode": "return",
"links": [],
"x": 2180,
"y": 511,
"wires": []
}
]
Hope this helps someone, thanks for getting this far!
Inspiration for the license came from Douglas Crockfords’ JSLint license, further discussion at Hacker News. ↩︎