{"id":15174066,"url":"https://github.com/plurid/action-mail","last_synced_at":"2026-02-07T06:03:20.844Z","repository":{"id":57137354,"uuid":"393683085","full_name":"plurid/action-mail","owner":"plurid","description":"Act on Mails","archived":false,"fork":false,"pushed_at":"2022-10-05T17:30:50.000Z","size":2306,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-20T20:41:27.715Z","etag":null,"topics":["actions","mail"],"latest_commit_sha":null,"homepage":"https://plurid.com/action-mail","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/plurid.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-08-07T12:52:55.000Z","updated_at":"2022-09-29T07:48:33.000Z","dependencies_parsed_at":"2022-09-01T03:00:58.579Z","dependency_job_id":null,"html_url":"https://github.com/plurid/action-mail","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/plurid/action-mail","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plurid%2Faction-mail","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plurid%2Faction-mail/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plurid%2Faction-mail/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plurid%2Faction-mail/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/plurid","download_url":"https://codeload.github.com/plurid/action-mail/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plurid%2Faction-mail/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29187969,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-07T05:07:31.176Z","status":"ssl_error","status_checked_at":"2026-02-07T05:06:15.227Z","response_time":63,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["actions","mail"],"created_at":"2024-09-27T11:23:47.361Z","updated_at":"2026-02-07T06:03:20.639Z","avatar_url":"https://github.com/plurid.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n    \u003ca target=\"_blank\" href=\"https://plurid.com/action-mail\"\u003e\n        \u003cimg src=\"https://raw.githubusercontent.com/plurid/action-mail/master/about/identity/action-mail-logo.png\" height=\"250px\"\u003e\n    \u003c/a\u003e\n    \u003cbr /\u003e\n    \u003cbr /\u003e\n    \u003ca target=\"_blank\" href=\"https://github.com/plurid/action-mail/blob/master/LICENSE\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/license-DEL-blue.svg?colorB=1380C3\u0026style=for-the-badge\" alt=\"License: DEL\"\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\n\n\n\u003ch1 align=\"center\"\u003e\n    action mail\n\u003c/h1\u003e\n\n\n\u003ch3 align=\"center\"\u003e\n    Act On Mails\n\u003c/h3\u003e\n\n\n\u003cbr /\u003e\n\n\n\n`action 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.\n\n`action entities` have `true` or `false` values. `action variables` have string values.\n\n`action mail`s 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.\n\nAn user is `accountless` when interacting with an internet service without being expressly identified to/for that service.\n\nAn 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.\n\n\n\n### Contents\n\n+ [Syntax](#syntax)\n+ [Usage](#usage)\n    + [Parser](#parser)\n        + [Dolphin Example](#dolphin-example)\n        + [World Example](#world-example)\n        + [Advanced](#advanced)\n    + [Clients](#clients)\n        + [Gmail](#gmail)\n+ [Packages](#packages)\n+ [Codeophon](#codeophon)\n\n\n\n## Syntax\n\nAn `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 token`s.\n\nAn `action mail field token` can be an `action mail entity` or an `action mail variable`.\n\nThe `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.\n\nThe `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' }`.\n\nAn `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 `spacer`s. The `spacer`s are customizable and specified by the service implementing action mails.\n\nThe leading and trailing whitespace is trimmed from action mail field `entities` and `variables`, e.g.\n\n```\n{     pay  }\n```\n\nis parsed as `{ pay: true }` and\n\n```\n{   one:   two  }\n```\n\nis parsed as `{ one: 'two' }`.\n\n\n\n## Usage\n\n### Parser\n\nIn-browser `action mail`s 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.\n\n\n#### Dolphin Example\n\nConsider the following dummy example with a `jsx` `BuyWithoutAccount` button\n\n``` tsx\nconst mail = 'address@example.com';\n\nconst requestSubject = encodeURIComponent(\n    `Hello from subject text`,\n);\n\nconst requestBody = encodeURIComponent(\n    `Hello,\\n\\nfrom body text\\n{using} an {action: mail} to {specify: variables} and {entities}.\\n`,\n);\n\nconst BuyWithoutAccount = () =\u003e (\n    \u003ca\n        href={`mailto:${mail}?subject=${requestSubject}\u0026body=${requestBody}`}\n    \u003e\n        \u003cbutton\u003e\n            Buy without Account\n        \u003c/button\u003e\n    \u003c/a\u003e\n);\n```\n\nWhen the user clicks the button, the browser will open their default mail client with the `destination`, `subject`, and `body` text already filled, such as\n\n```\nTo: address@example.com\nSubject: Hello from subject text\nBody: Hello,\n\nfrom body text\n{using} an {action: mail} to {specify: variables} and {entities}.\n```\n\nAfter the mail reaches the destination, it will be parsed and the following data structures is obtained\n\n``` json\n{\n    \"using\": true,\n    \"action\": \"mail\",\n    \"specify\": \"variables\",\n    \"entities\": true\n}\n```\n\nThe data structure can then be used to `POST` an API endpoint which will take care of responding accordingly to the `action mail`.\n\nThe [mail client add-ons](#clients) handle parsing and calling the adequate API endpoint, with the appropriate authorization token.\n\n\n#### World Example\n\nA sales `action mail` could use the following `subject` and `body`.\n\n``` typescript\nconst requestSubject = encodeURIComponent(\n    `[Order] Product x1 for $100`,\n);\n\nconst requestBody = encodeURIComponent(\n`Hello,\n\n\nI would like to order\n\n    {Product: x1 · specifications: of product}\n\nPlease {send} me a payment link\n\nand {do not generate} an account using this email.\n\nDeliver with the following shipment details\n\n    {name: }\n    {country: }\n    {city: }\n    {street: }\n\n\nThanks\n`);\n```\n\nwhich when parsed with the following settings\n\n``` typescript\nimport {\n    parser,\n} from '@plurid/action-mail-parser';\n\n\nconst data = requestSubject + requestBody;\n\nconst values = parser(\n    data,\n    {\n        spacer: '·',\n        fielders: [\n            ['{', '}'],\n            ['[', ']'],\n        ],\n        camelCaseKeys: true,\n    },\n);\n```\n\nobtains the data structure\n\n``` json\n{\n    \"order\": true,\n    \"send\": true,\n    \"generate\": false,\n    \"name\": \"\",\n    \"country\": \"\",\n    \"city\": \"\",\n    \"street\": \"\",\n    \"groups\": [\n        {\n            \"product\": \"x1\",\n            \"specifications\": \"of product\"\n        }\n    ]\n}\n```\n\nThe 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.\n\n\n#### Advanced\n\nThe `parser` options are\n\n``` typescript\ninterface ParserOptions {\n    /**\n     * Delimiting string between multiple fields within the same action mail group.\n     *\n     * e.g. `{one: two · three: four}` with `·` as spacer.\n     *\n     * Default: none\n     */\n    spacer: string;\n\n    /**\n     * The name of the groups key.\n     *\n     * Default: `'groups'`\n     */\n    groupsKey: string;\n\n    /**\n     * Use `camelCase` for all the keys.\n     *\n     * Default: `false`\n     */\n    camelCaseKeys: boolean;\n\n    /**\n     * Field start-end pairs.\n     *\n     * The first element marks field start, the second element marks field end.\n     *\n     * Default: `[ ['{', '}'] ]`\n     */\n    fielders: string[][];\n\n    /**\n     * Negation words (strings or regular expressions) to negate the value of a field.\n     *\n     * e.g. `{no foo}` gives `{ foo: false }`.\n     *\n     * Default: `[ 'no', 'not', 'none', 'don\\'t', 'do not' ]`\n     */\n    negations: (string | RegExp)[];\n}\n```\n\n\nWhen parsing multiple types of data, a `Registry` can be used to easily determine the data structure type.\n\n\n``` typescript\nimport {\n    Registry,\n} from '@plurid/action-mail-parser';\n\n\nconst main = () =\u003e {\n    const registry = new Registry();\n\n    registry.register({\n        type: 'one',\n        shape: {\n            two: 'string',\n            three: 'boolean',\n        },\n    });\n\n\n    interface DataOne {\n        two: string;\n        three: boolean;\n    }\n\n    const dataOne = `one {two: data one} {three}`;\n    /**\n     * {\n     *   type: 'one',\n     *   values: {\n     *     two: 'data one',\n     *     three: true,\n     *   },\n     * }\n     */\n    const parseOne = registry.parse\u003cDataOne\u003e(dataOne);\n}\n\nmain();\n```\n\n\n### Clients\n\nThe `action mail` client listens for new mails, parses them accordingly, and sends the parsed data to the appropriate API `endpoint`.\n\n\n### Gmail\n\nThe [Gmail Action Mail]((https://plurid.com/action-mail)) client is a Google Workspace Add-on which can be installed in Gmail.\n\nTo use the Action Mail, `Add Mail` providing the required information: `to mail`, `endpoint`, `token`, `public key`.\n\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/plurid/action-mail/master/about/clients/presentation/action-mail-gmail-add.png\" height=\"900px\"\u003e\n\u003c/p\u003e\n\n\nThe `to mail` is the mail which receives action mails, if multiple mails configured in Gmail, e.g. `example@gmail.com`.\n\n\nThe `endpoint` is a `https` API endpoint, e.g. `https://api.example.com`. The endpoint can be `REST` or `GraphQL`.\n\nThe `GraphQL` types are\n\n``` graphql\nmutation ActionMailCall(input: ActionMailCallInput!): ActionMailResponse!\n\n\ninput ActionMailCallInput {\n    data: ActionMailCallInputCrypted!\n    metadata: ActionMailCallInputCrypted!\n    token: String\n}\n\ninput ActionMailCallInputCrypted {\n    aes: String!\n    text: String!\n}\n\n\ntype ActionMailResponse {\n    status: Boolean!\n}\n```\n\n\nThe `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 \u003ctoken\u003e`.\n\nDue to a limitation in dynamically whitelisting URLs for the [URL Fetch Google Apps Script Service](https://developers.google.com/apps-script/manifest#Manifest.FIELDS.urlFetchWhitelist), the configured `endpoint` will be called from `https://api.plurid.com`.\n\n\nThe `public key` will be used to encrypt the `data` and `metadata` on the add-on side.\n\nGenerate private/public key pairs using\n\n```\n# generate private key\nopenssl genrsa -des3 -out action-mail.private.pem 4096\n\n# extract public key\nopenssl rsa -in action-mail.private.pem -outform PEM -pubout -out action-mail.public.pem\n```\n\nThe encryption scheme is hybrid:\n\n+ the `data` and the `metadata` objects are converted to strings,\n+ unique AES keys are generated to encrypt the values,\n+ the generated AES keys are encrypted with the `public key` and sent together to the `endpoint`.\n\nThe call interface is\n\n``` typescript\ninterface ActionMailCall {\n    data: {\n        aes: string;\n        text: string;\n    };\n    metadata: {\n        aes: string;\n        text: string;\n    };\n    token: string;\n}\n```\n\nThe `metadata` interface is\n\n``` typescript\ninterface ActionMailMetadata {\n    id: string;\n    parsedAt: number;\n    message: ActionMailMetadataMessage;\n}\n\ninterface ActionMailMetadataMessage {\n    id: string;\n    sender: string;\n    receiver: string;\n    subject: string\n    body: string\n    date: number;\n    attachments: ActionMailMetadataAttachment[];\n}\n\ninterface ActionMailMetadataAttachment {\n    name: string;\n    hash: string;\n    size: number;\n    contentType: string;\n    bytes: number[];\n}\n```\n\n\nA decryption implementation in NodeJS could look like this (see more in `fixtures/test-endpoint`).\n\n\n``` typescript\nimport crypto from 'crypto';\nimport CryptoJS from 'crypto-js';\n\n\ninterface Crypted {\n    text: string;\n    aes: string;\n}\n\n\nconst privateKey = `-----BEGIN RSA PRIVATE KEY-----...`;\nconst keyPassphrase = 'secretPassphrase';\n\n\nconst decryptAes = (\n    aes: string,\n    privateKey: string,\n    keyPassphrase: string,\n) =\u003e {\n    const aesKey = crypto.privateDecrypt(\n        {\n            key: privateKey,\n            padding: crypto.constants.RSA_PKCS1_PADDING,\n            passphrase: keyPassphrase,\n        },\n        Buffer.from(aes, 'base64'),\n    );\n\n    return aesKey.toString();\n}\n\nconst decryptLoad = (\n    aesKey: string,\n    load: string,\n) =\u003e {\n    const aesBytes = CryptoJS.AES.decrypt(load, aesKey.toString());\n    const clearText = JSON.parse(aesBytes.toString(CryptoJS.enc.Utf8));\n\n    return clearText;\n}\n\nconst decrypt = (\n    crypted: Crypted,\n): any =\u003e {\n    const {\n        aes,\n        text,\n    } = crypted;\n\n    const aesKey = decryptAes(\n        aes,\n        privateKey,\n        keyPassphrase,\n    );\n\n    const load = decryptLoad(\n        aesKey,\n        text,\n    );\n\n    return load;\n}\n```\n\n\nThe `gateway token` is required to use extended features, such as increased bandwidth to account for attachments. See more on [plurid.com/action-mail](https://plurid.com/action-mail).\n\n\nThe `attachments`, if any, can be saved to the filesystem, given the correctly `JSON.parse`d `metadata` object.\n\n``` typescript\nimport {\n    promises as fs,\n} from 'fs';\n\nfor (const attachment of metadata.message.attachments) {\n    const {\n        name,\n        bytes,\n    } = attachment;\n\n    fs.writeFile(\n        name,\n        Buffer.from(bytes),\n    );\n}\n```\n\n\n\n## Packages\n\n\n\u003ca target=\"_blank\" href=\"https://www.npmjs.com/package/@plurid/action-mail-parser\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/v/@plurid/action-mail-parser.svg?logo=npm\u0026colorB=1380C3\u0026style=for-the-badge\" alt=\"Version\"\u003e\n\u003c/a\u003e\n\n[@plurid/action-mail-parser][action-mail-parser] • parser\n\n[action-mail-parser]: https://github.com/plurid/action-mail/tree/master/packages/action-mail-parser\n\n\n[@plurid/action-mail-gmail][action-mail-gmail] • gmail mail client\n\n[action-mail-gmail]: https://github.com/plurid/action-mail/tree/master/packages/action-mail-gmail\n\n\n\n## [Codeophon](https://github.com/ly3xqhl8g9/codeophon)\n\n+ licensing: [delicense](https://github.com/ly3xqhl8g9/delicense)\n+ versioning: [αver](https://github.com/ly3xqhl8g9/alpha-versioning)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplurid%2Faction-mail","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fplurid%2Faction-mail","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplurid%2Faction-mail/lists"}