You'll need to have access to an email address that can be used. This implies also having access to a domain name. This can be challenging if you are running a node pico engine locally.
Making a localhost engine available to the outside world
Here at Pico Labs, we have a pico engine running on a Rapberry Pi 3, and make it available to the outside world by using ngrok which supplies "secure introspectable tunnels to localhost". This means that our engine can be accessed at https://assigned_id_1.ngrok.io
etc. where "assigned_id_1" is a pseudo-random identifier given to us by ngrok. (The actual ID is redacted here, to avoid denial of service attacks, because ngrok only allows us 20 uses per minute.) Another alternative is PageKite, but we'll use ngrok in this cookbook.
Obtaining an email address that forwards to a webhook
Here we will show how to use CloudMailin which is a cloud service that assigns us an email address, which we can configure so that it does an HTTP POST to a webhook that we supply. We redact the actual address as "assigned_id_2" because CloudMailin only allows us 200 incoming email messages per month. The address we are using is assigned_id_2@cloudmailin.net
We have configured it to use https://assigned_id_1
.ngrok.io/sky/event/assigned_id_3/eid/mail/received
where "assigned_id_3" is an ECI (event channel identifier) which we created for the pico which we will be using to respond to mail:received
events.
It should also be possible to install, configure, and run your own mailin server, but here we will use the CloudMailin service.
A ruleset that handles the mail:received
event
We created a pico, named Mail, and added a new channel with type
"secret" and name
"incoming mail", whose id
is known here as assigned_id_3, and added a ruleset containing this rule
rule mail_incoming { select when mail received pre { attrs = event:attrs().klog("attrs") headers = attrs{"headers"} date = headers{"Date"}.klog("date") to = headers{"To"}.klog("to") from = headers{"From"}.klog("from") subject = headers{"Subject"}.klog("subject") text = attrs{"plain"}.klog("text") } fired { raise mail event "parsed" attributes { "date":date, "to":to, "from":from, "subject":subject, "text":text } } }
This rule logs many things (and these log messages could be removed in a production application), extracts a few simple items from the incoming message and headers, and raises a new mail:parsed
event supplying those items as event attributes. You may wish to add rules in this or another ruleset for use within the Mail pico to respond to the mail:parsed
event. In our case, we wanted to pass the event on to another pico.
Forwarding to another pico
For this purpose, the same ruleset also contains a rule which is triggered by this mail:parsed
event, which parses out the ECI of the other pico from the "To" email address.
rule mail_reaction { select when mail parsed to re#assigned_id_2[+]([^@]*)@cloudmailin.net# setting(eci) event:send({"eci":eci, "domain":"test", "type":"test", "attrs":event:attrs()}) }
This rule is only selected when the "to" address matches the regular expression in line 2. The construct [+]
matches a literal plus sign, and the capture group ([^@]*)
matches all characters up to the next at sign. The setting
clause binds the captured characters as the value of the eci
identifier. This ECI is how we refer to the pico which is to handle the incoming email message.
This means that we must send our email message to an address looking like
assigned_id_2+assigned_id_4@cloudmailin.net
The redacted "assigned_id_4" is an ECI of another pico to which the rule (in line 3) sends the test:test
event with parsed fields from the email message as attributes. We created a new channel for this purpose, with name
"email tests" and type
"secret" whose id
is known herein as "assigned_id_4". This is easily done in the "Channels" tab of the developer UI.
Further processing of an incoming email message
In this example, the other pico has a ruleset which handles numerous events with domain "aurora" and controls a set of 12 Nanoleaf Aurora units. Rather than modify the existing ruleset, we make a new ruleset and add it to the same pico. Shown here is a global definition and a rule that reacts to the test:test
event.
global { newline = (13.chr() + "?" + 10.chr()).as("RegExp") } rule test_test { select when test test foreach event:attr("text").split(newline) setting(request) pre { parts = request.klog("REQUESTED_EVENT").split(re#/#) aurora = parts.length() == 2 && parts[0] == "aurora" } if aurora then noop() fired { raise aurora event parts[1] } }
The regular expression created in line 2 will match a carriage return (if present) followed by a newline. It is used in line 6 to split the text into an array of the lines of the email message body. The remainder of the rule is evaluated once for each line of the message, with the name request
bound to the line under consideration.
Each line is split into parts
using a forward slash as a separator. We ignore any lines which do not begin "aurora/" followed by just one slash-separated string of characters. They are ignored because the rule won't fire for such lines. For lines which match our expectation, in line 13 we raise an aurora event whose type is parts[1]
(the second slash-separated string of characters).
Sample email message
A sample email message might look like this:
From: ... To: assigned_id_2+assigned_id_4@cloudmailin.net Subject: anything -- ignored by these rules This comment/annotation is ignored The next line turns on the aurora nanoleaves aurora/on Then we move on to the second next stored theme aurora/next aurora/next
Upon receipt of this email message, our Mail pico will extract the "To" field and the "text" of the message body, and begin a cascade of events. Next, the same pico will parse out the ECI ("assigned_id_4") of the Aurora pico and send it the test:test
event. This will trigger its new rule which will loop over the lines of the message body, ignoring comment lines, and raise a series of three events, which the existing ruleset will handle to turn the NanoLeaf display on and move through to the second-next theme.
The person composing the email message needs some knowledge of the events understood by the Aurora pico, but doesn't need to know where its pico is hosted, just the email address to which the message must be sent.
Caveats
In general, the most difficult part of turning an incoming email message into an event is parsing the message body to determine what further events need to be raised.
Notice that email clients differ. For example, the OWA email client running in a Chrome browser will insert a zero-width space character into the message body if the user tabs to it from the subject area.This is just one potential problem which could cause you grief.
You will want to thoroughly test your rules before putting this cookbook into production.