Action Mail Parser
npm install @plurid/action-mail-parseraction mail provides a specification, parsing tools, and mail client utilities to obtain action entities and/or action variables from mails, messages, or other user input.
action entities have true or false values. action variables have string values.
action mails are intended to be used for accountless interactions, however the parsing functionality can be used to extract and perform actions for fully authenticated users using any realtime communication channel.
An user is accountless when interacting with an internet service without being expressly identified to/for that service.
An user could desire to be accountless and at the same time be interested in a particular product offered by an internet service. They could even desire to purchase that particular product, but without going through the hassle of setting up an account, or exposing their favorite login with provider for just another, simple, one-time buy event. They want a flea market-like economic transaction, not to engage in a 12 year relationship with a prenuptial agreement and a tough divorce.
+ Syntax
+ Usage
+ Parser
+ Dolphin Example
+ World Example
+ Advanced
+ Clients
+ Gmail
+ Packages
+ Codeophon
An action mail field content is enclosed in { and }, or in [ and ], or in other characters that have a general delimiting semantic, customizable and specified by the service implementing action mails. The action mail field content can consist of one or more action mail field tokens.
An action mail field token can be an action mail entity or an action mail variable.
The action mail entity expresses the truth or falseness of a concept, e.g. {send}, {generate}. action mail entities can be negated by prefixing the token with common language words: don't, no, none; e.g. {don't send}. The negations can be extended and specified by the service implementing action mails.
The action mail variables expect a string value after the colon, e.g. {name: one}, {zip code: 012345}. The value is generally left to be filled by the user, but it can contain predefined values. The parsing of the string value into types, i.e. numbers, is left to the service implementing action mails, e.g. { one: 123 } is parsed as { one: '123' }.
An action mail field can have multiple entities and/or variables, using a spacer to distinguish them, e.g. {send · name: one}, {generate, don't send}, with · and , acting as spacers. The spacers are customizable and specified by the service implementing action mails.
The leading and trailing whitespace is trimmed from action mail field entities and variables, e.g.
```
{ pay }
is parsed as { pay: true } and
``
{ one: two }
is parsed as { one: 'two' }.
In-browser action mails are composed on the client-side using an anchor a tag with the href attribute set to mailto, where the query values of subject and body are encodeURIComponent strings with the action mail syntax interspersing the text.
#### Dolphin Example
Consider the following dummy example with a jsx BuyWithoutAccount button
` tsx
const mail = 'address@example.com';
const requestSubject = encodeURIComponent(
Hello from subject text,
);
const requestBody = encodeURIComponent(
Hello,\n\nfrom body text\n{using} an {action: mail} to {specify: variables} and {entities}.\n,
);
const BuyWithoutAccount = () => (
href={mailto:${mail}?subject=${requestSubject}&body=${requestBody}}`
>
);
When the user clicks the button, the browser will open their default mail client with the destination, subject, and body text already filled, such as
`
To: address@example.com
Subject: Hello from subject text
Body: Hello,
from body text
{using} an {action: mail} to {specify: variables} and {entities}.
`
After the mail reaches the destination, it will be parsed and the following data structures is obtained
` json`
{
"using": true,
"action": "mail",
"specify": "variables",
"entities": true
}
The data structure can then be used to POST an API endpoint which will take care of responding accordingly to the action mail.
The mail client add-ons handle parsing and calling the adequate API endpoint, with the appropriate authorization token.
#### World Example
A sales action mail could use the following subject and body.
` typescript[Order] Product x1 for $100
const requestSubject = encodeURIComponent(
,
);
const requestBody = encodeURIComponent(
Hello,
I would like to order
{Product: x1 · specifications: of product}
Please {send} me a payment link
and {do not generate} an account using this email.
Deliver with the following shipment details
{name: }
{country: }
{city: }
{street: }
Thanks);`
which when parsed with the following settings
` typescript
import {
parser,
} from '@plurid/action-mail-parser';
const data = requestSubject + requestBody;
const values = parser(
data,
{
spacer: '·',
fielders: [
['{', '}'],
['[', ']'],
],
camelCaseKeys: true,
},
);
`
obtains the data structure
` json`
{
"order": true,
"send": true,
"generate": false,
"name": "",
"country": "",
"city": "",
"street": "",
"groups": [
{
"product": "x1",
"specifications": "of product"
}
]
}
The user only has to complete the name, country, city, street fields, or they could be autofilled with predefined values if they also use the action mail mail client.
#### Advanced
The parser options are
` typescript{one: two · three: four}
interface ParserOptions {
/**
* Delimiting string between multiple fields within the same action mail group.
*
* e.g. with · as spacer.
*
* Default: none
*/
spacer: string;
/**
* The name of the groups key.
*
* Default: 'groups'
*/
groupsKey: string;
/**
* Use camelCase for all the keys.false
*
* Default:
*/
camelCaseKeys: boolean;
/**
* Field start-end pairs.
*
* The first element marks field start, the second element marks field end.
*
* Default: [ ['{', '}'] ]
*/
fielders: string[][];
/**
* Negation words (strings or regular expressions) to negate the value of a field.
*
* e.g. {no foo} gives { foo: false }.[ 'no', 'not', 'none', 'don\'t', 'do not' ]
*
* Default: `
*/
negations: (string | RegExp)[];
}
When parsing multiple types of data, a Registry can be used to easily determine the data structure type.
` typescript
import {
Registry,
} from '@plurid/action-mail-parser';
const main = () => {
const registry = new Registry();
registry.register({
type: 'one',
shape: {
two: 'string',
three: 'boolean',
},
});
interface DataOne {
two: string;
three: boolean;
}
const dataOne = one {two: data one} {three};
/**
* {
* type: 'one',
* values: {
* two: 'data one',
* three: true,
* },
* }
*/
const parseOne = registry.parse
}
main();
`
The action mail client listens for new mails, parses them accordingly, and sends the parsed data to the appropriate API endpoint.
The Gmail Action Mail) client is a Google Workspace Add-on which can be installed in Gmail.
To use the Action Mail, Add Mail providing the required information: to mail, endpoint, token, public key.

The to mail is the mail which receives action mails, if multiple mails configured in Gmail, e.g. example@gmail.com.
The endpoint is a https API endpoint, e.g. https://api.example.com. The endpoint can be REST or GraphQL.
The GraphQL types are
` graphql
mutation ActionMailCall(input: ActionMailCallInput!): ActionMailResponse!
input ActionMailCallInput {
data: ActionMailCallInputCrypted!
metadata: ActionMailCallInputCrypted!
token: String
}
input ActionMailCallInputCrypted {
aes: String!
text: String!
}
type ActionMailResponse {
status: Boolean!
}
`
The token is generated and will be checked on the endpoint-side and can be attached to the payload or added as a bearer header, e.g. Authorization: Bearer .
Due to a limitation in dynamically whitelisting URLs for the URL Fetch Google Apps Script Service, the configured endpoint will be called from https://api.plurid.com.
The public key will be used to encrypt the data and metadata on the add-on side.
Generate private/public key pairs using
`generate private key
openssl genrsa -des3 -out action-mail.private.pem 4096
The encryption scheme is hybrid:
+ the
data and the metadata objects are converted to strings,
+ unique AES keys are generated to encrypt the values,
+ the generated AES keys are encrypted with the public key and sent together to the endpoint.The call interface is
` typescript
interface ActionMailCall {
data: {
aes: string;
text: string;
};
metadata: {
aes: string;
text: string;
};
token: string;
}
`The
metadata interface is` typescript
interface ActionMailMetadata {
id: string;
parsedAt: number;
message: ActionMailMetadataMessage;
}interface ActionMailMetadataMessage {
id: string;
sender: string;
receiver: string;
subject: string
body: string
date: number;
attachments: ActionMailMetadataAttachment[];
}
interface ActionMailMetadataAttachment {
name: string;
hash: string;
size: number;
contentType: string;
bytes: number[];
}
`
A decryption implementation in NodeJS could look like this (see more in
fixtures/test-endpoint).
` typescript
import crypto from 'crypto';
import CryptoJS from 'crypto-js';
interface Crypted {
text: string;
aes: string;
}
const privateKey =
-----BEGIN RSA PRIVATE KEY-----...;
const keyPassphrase = 'secretPassphrase';
const decryptAes = (
aes: string,
privateKey: string,
keyPassphrase: string,
) => {
const aesKey = crypto.privateDecrypt(
{
key: privateKey,
padding: crypto.constants.RSA_PKCS1_PADDING,
passphrase: keyPassphrase,
},
Buffer.from(aes, 'base64'),
);
return aesKey.toString();
}
const decryptLoad = (
aesKey: string,
load: string,
) => {
const aesBytes = CryptoJS.AES.decrypt(load, aesKey.toString());
const clearText = JSON.parse(aesBytes.toString(CryptoJS.enc.Utf8));
return clearText;
}
const decrypt = (
crypted: Crypted,
): any => {
const {
aes,
text,
} = crypted;
const aesKey = decryptAes(
aes,
privateKey,
keyPassphrase,
);
const load = decryptLoad(
aesKey,
text,
);
return load;
}
`
The
gateway token is required to use extended features, such as increased bandwidth to account for attachments. See more on plurid.com/action-mail.
The
attachments, if any, can be saved to the filesystem, given the correctly JSON.parsed metadata object.` typescript
import {
promises as fs,
} from 'fs';for (const attachment of metadata.message.attachments) {
const {
name,
bytes,
} = attachment;
fs.writeFile(
name,
Buffer.from(bytes),
);
}
``[@plurid/action-mail-parser][action-mail-parser] • parser
[action-mail-parser]: https://github.com/plurid/action-mail/tree/master/packages/action-mail-parser
[@plurid/action-mail-gmail][action-mail-gmail] • gmail mail client
[action-mail-gmail]: https://github.com/plurid/action-mail/tree/master/packages/action-mail-gmail