@messagebird/n8n-nodes-bird 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bird
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # @messagebird/n8n-nodes-bird
2
+
3
+ The official [Bird](https://bird.com) node for [n8n](https://n8n.io). Send transactional email and SMS, look up delivery status, browse SMS templates, and wire Bird into your workflows.
4
+
5
+ 📚 **Bird API documentation:** https://bird.com/docs
6
+
7
+ ## Installation
8
+
9
+ Open the nodes panel in any workflow, search for **Bird**, and select **Install**.
10
+
11
+ On a self-hosted instance that has not enabled community nodes in the panel, use **Settings → Community Nodes → Install** and enter `@messagebird/n8n-nodes-bird`. Requires n8n 2.x on Node.js 20 or later.
12
+
13
+ ## Operations
14
+
15
+ The **Bird** node covers the public Bird API: email and SMS sends, delivery status and event timelines, WhatsApp, verification, templates, sending domains, suppressions, inbound routes, webhooks, statistics, lookup, and more. Resources and operations mirror the API reference at https://bird.com/docs/api. Three areas are deliberately out: publishing and subscribing on Realtime, which needs an app key and secret this credential cannot hold (managing Realtime apps and keys is included); the attachment and media downloads, which return bytes rather than the JSON a workflow item carries; and redirect-based media fetches.
16
+
17
+ <details>
18
+ <summary><strong>177 operations across 22 resources</strong></summary>
19
+
20
+ - **Audience** (9) — Add Contacts, Create, Delete, Get, List, List Contacts, Remove Contact, Remove Contacts, Update
21
+ - **Contact** (7) — Batch, Create, Delete, Get, List, List Preferences, Update
22
+ - **Contact Property** (6) — Archive, Create, Get, List, Unarchive, Update
23
+ - **Doc** (2) — Read, Search
24
+ - **Domain** (9) — Create, Delete, Get, List, List Events, Release Tracking, Share DNS Records, Update, Verify
25
+ - **Email** (57) — Cancel, Content, Create Inbound Route, Create Mailbox, Create Mailbox Message, Create Mailbox Receive Rule, Delete Inbound Route, Delete Mailbox, Delete Mailbox Receive Rule, Delete Thread, Delete Thread Message, Get, Get Inbound Route, Get Mailbox, Get Mailbox Statistics, Get SMTP Config, Get Thread, Get Thread Message, Get Thread Message Body, List, List Events, List Inbound Routes, List Mailbox Labels, List Mailbox Messages, List Mailbox Receive Rules, List Mailboxes, List SMTP Configs, List Thread Message Attachments, List Thread Messages, List Threads, Recipients, Reply to Thread Message, Restore Mailbox, Resume Mailbox, Send, Send Batch, Stats By Bounce Code, Stats By Broadcast, Stats By Category, Stats By Client, Stats By Complaint Type, Stats By Location, Stats By Mailbox Provider, Stats By Mailbox Provider Region, Stats By Recipient Domain, Stats By Sending Domain, Stats By Sending IP, Stats By Tag, Stats By Template, Stats Daily, Stats Hourly, Stats Summary, Update Inbound Route, Update Mailbox, Update SMTP Config, Update Thread, Update Thread Message
26
+ - **Inbound Address** (5) — Create, Delete, Get, List, Update
27
+ - **Inbound Message** (4) — Attachments, Body, Get, List
28
+ - **Lookup** (2) — Email, Phone Number
29
+ - **Number** (8) — Create Order, Get, Get Available Number, Get Order, List, List Available Numbers, List Orders, Release
30
+ - **Preference** (4) — Create, Delete, Get, List
31
+ - **Realtime** (9) — Create App, Create App Key, Delete App, Get App, List App Keys, List Apps, List Regions, Revoke App Key, Update App
32
+ - **SMS** (21) — Get, List, List Events, Send, Send Batch, Stats By Carrier, Stats By Category, Stats By Country, Stats By Error Code, Stats By Originator, Stats By Status, Stats By Tag, Stats Daily, Stats Hourly, Stats Inbound By Country, Stats Inbound By Number, Stats Inbound By Operator, Stats Inbound Daily, Stats Inbound Hourly, Stats Inbound Summary, Stats Summary
33
+ - **SMS Keyword Rule** (5) — Create, Delete, Get, List, Update
34
+ - **SMS Suppression** (4) — Add, Get, List, Remove
35
+ - **SMS Template** (2) — Get, List
36
+ - **Suppression** (4) — Add, Get, List, Remove
37
+ - **Verify** (3) — Create Next Channel Attempt, Create Verification, Create Verification Check
38
+ - **Voice** (2) — Get, List
39
+ - **Webhook** (9) — Attempts, Create, Delete, Get, List, Replay, Rotate Secret, Test, Update
40
+ - **WhatsApp** (4) — Get, List, List Events, Send
41
+ - **Workspace** (1) — Get
42
+
43
+ </details>
44
+
45
+ ## Trigger
46
+
47
+ The **Bird Trigger** node starts a workflow when Bird delivers a subscribed event (an email delivered, a bounce, an inbound SMS, and 50 more). It registers the webhook automatically with your API key, verifies every delivery as a [Standard Webhook](https://www.standardwebhooks.com) before the workflow runs, and deduplicates redelivered events. One Bird webhook is created per workflow; workspace webhook quotas apply.
48
+
49
+ ## Credentials
50
+
51
+ Create an API key in the Bird dashboard under **API keys**, near the bottom of the sidebar, and paste it into the **Bird API** credential. The key is all the node needs — nothing else to configure. The credential's help text lists the scopes the key needs; the credential test verifies the key against your workspace.
52
+
53
+ ## Limitations
54
+
55
+ An email has to fit in 20 MB once assembled, counting each attachment at its larger base64-encoded size.
56
+
57
+ ## License
58
+
59
+ [MIT](LICENSE)
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BirdApi = void 0;
4
+ const scopes_gen_1 = require("../nodes/Bird/generated/scopes.gen");
5
+ class BirdApi {
6
+ name = "birdApi";
7
+ displayName = "Bird API";
8
+ icon = {
9
+ light: "file:bird.svg",
10
+ dark: "file:bird-dark.svg",
11
+ };
12
+ documentationUrl = "https://bird.com/docs";
13
+ properties = [
14
+ {
15
+ displayName: "API Key",
16
+ name: "apiKey",
17
+ type: "string",
18
+ typeOptions: { password: true },
19
+ required: true,
20
+ default: "",
21
+ description: `A Bird API key, created in the Bird dashboard under API keys, near the bottom of the sidebar. The key is all this node needs — there is nothing else to configure. Grant the scopes covering the operations your workflows use; the full set this node can call: ${scopes_gen_1.CREDENTIAL_SCOPES.join(", ")}.`,
22
+ },
23
+ ];
24
+ authenticate = {
25
+ type: "generic",
26
+ properties: {
27
+ headers: {
28
+ Authorization: "=Bearer {{$credentials.apiKey}}",
29
+ },
30
+ },
31
+ };
32
+ // The base URL is an expression over the key itself — the same derivation
33
+ // transport.ts does — because the region must never be asked.
34
+ test = {
35
+ request: {
36
+ baseURL: '=https://{{$credentials.apiKey.split("_")[1]}}.platform.bird.com',
37
+ url: "/v1/workspace",
38
+ },
39
+ // A 403 here means the key authenticated but lacks the probe's scope —
40
+ // without this rule n8n shows "Forbidden - perhaps check your
41
+ // credentials?", which reads as a bad key.
42
+ rules: [
43
+ {
44
+ type: "responseCode",
45
+ properties: {
46
+ value: 403,
47
+ message: "The API key is valid but has not been granted the workspace scope (read). Grant it in the Bird dashboard under API keys, near the bottom of the sidebar, then retest.",
48
+ },
49
+ },
50
+ ],
51
+ };
52
+ }
53
+ exports.BirdApi = BirdApi;
@@ -0,0 +1,4 @@
1
+ <svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="80" height="80" fill="white"/>
3
+ <path d="M58.2891 27.1368C56.5065 27.1368 54.9258 28.0817 53.9619 29.5378L47.423 39.4006C47.1848 39.7618 46.7888 40 46.3424 40C45.6073 40 45.0125 39.3595 45.0125 38.5692C45.0125 38.2742 45.093 37.9997 45.2334 37.7741L50.7563 28.8816C51.8433 27.1289 51.9458 24.8746 51.0214 23.0179C50.0985 21.1595 48.3001 20 46.3424 20H17.1406V25.7123H42.3638C42.3638 27.2867 41.1728 28.5692 39.7104 28.5692H17.1406C17.1406 30.5995 17.535 32.5304 18.2496 34.2815H37.0554C37.0554 35.8558 35.8644 37.1368 34.402 37.1368H19.794C22.2171 40.6058 26.0726 42.8506 30.4172 42.8506H38.379C39.1141 42.8506 39.7104 43.4911 39.7104 44.2815C39.7104 45.0718 39.1141 45.7123 38.379 45.7123H30.4172L21.5483 60H39.0479C46.3944 60 52.632 54.8557 54.8106 47.7378L57.8016 37.9808C58.7702 34.8131 60.5418 32.0366 62.8592 29.9306C61.9253 28.2553 60.2294 27.1368 58.2891 27.1368ZM58.2891 31.068C57.7401 31.068 57.2952 30.59 57.2952 30C57.2952 29.41 57.7401 28.9305 58.2891 28.9305C58.8412 28.9305 59.2813 29.4116 59.2813 30C59.2877 30.5869 58.8412 31.068 58.2891 31.068Z" fill="black"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="80" height="80" fill="black"/>
3
+ <path d="M58.2891 27.1368C56.5065 27.1368 54.9258 28.0818 53.9619 29.5378L47.423 39.4006C47.1848 39.7618 46.7888 40 46.3424 40C45.6073 40 45.0125 39.3596 45.0125 38.5692C45.0125 38.2742 45.093 37.9997 45.2334 37.7741L50.7563 28.8816C51.8433 27.1289 51.9458 24.8746 51.0214 23.0179C50.0985 21.1595 48.3001 20 46.3424 20H17.1406V25.7123H42.3638C42.3638 27.2867 41.1728 28.5692 39.7104 28.5692H17.1406C17.1406 30.5995 17.535 32.5304 18.2496 34.2815H37.0554C37.0554 35.8559 35.8644 37.1368 34.402 37.1368H19.794C22.2171 40.6058 26.0726 42.8507 30.4172 42.8507H38.379C39.1141 42.8507 39.7104 43.4911 39.7104 44.2815C39.7104 45.0718 39.1141 45.7123 38.379 45.7123H30.4172L21.5483 60H39.0479C46.3944 60 52.632 54.8557 54.8106 47.7379L57.8016 37.9808C58.7702 34.8131 60.5418 32.0366 62.8592 29.9306C61.9253 28.2553 60.2294 27.1368 58.2891 27.1368ZM58.2891 31.068C57.7401 31.068 57.2952 30.59 57.2952 30C57.2952 29.41 57.7401 28.9305 58.2891 28.9305C58.8412 28.9305 59.2813 29.4116 59.2813 30C59.2877 30.5869 58.8412 31.068 58.2891 31.068Z" fill="white"/>
4
+ </svg>
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BirdApi = exports.BirdTrigger = exports.Bird = void 0;
4
+ var Bird_node_1 = require("./nodes/Bird/Bird.node");
5
+ Object.defineProperty(exports, "Bird", { enumerable: true, get: function () { return Bird_node_1.Bird; } });
6
+ var BirdTrigger_node_1 = require("./nodes/Bird/BirdTrigger.node");
7
+ Object.defineProperty(exports, "BirdTrigger", { enumerable: true, get: function () { return BirdTrigger_node_1.BirdTrigger; } });
8
+ var BirdApi_credentials_1 = require("./credentials/BirdApi.credentials");
9
+ Object.defineProperty(exports, "BirdApi", { enumerable: true, get: function () { return BirdApi_credentials_1.BirdApi; } });
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Bird = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const properties_gen_1 = require("./generated/properties.gen");
6
+ const routing_gen_1 = require("./generated/routing.gen");
7
+ const pickers_1 = require("./pickers");
8
+ const transport_1 = require("./transport");
9
+ class Bird {
10
+ description = {
11
+ displayName: "Bird",
12
+ name: "bird",
13
+ icon: { light: "file:bird.svg", dark: "file:bird-dark.svg" },
14
+ group: ["output"],
15
+ version: 1,
16
+ subtitle: '={{ $parameter["operation"] + ": " + $parameter["resource"] }}',
17
+ description: "Send email over Bird and read your sending data",
18
+ defaults: { name: "Bird" },
19
+ inputs: [n8n_workflow_1.NodeConnectionTypes.Main],
20
+ outputs: [n8n_workflow_1.NodeConnectionTypes.Main],
21
+ usableAsTool: true,
22
+ credentials: [{ name: "birdApi", required: true }],
23
+ properties: properties_gen_1.birdProperties,
24
+ };
25
+ methods = { listSearch: (0, pickers_1.birdListSearch)() };
26
+ async execute() {
27
+ const items = this.getInputData();
28
+ const out = [];
29
+ for (let i = 0; i < items.length; i++) {
30
+ try {
31
+ const resource = this.getNodeParameter("resource", i);
32
+ const operation = this.getNodeParameter("operation", i);
33
+ const route = routing_gen_1.birdRoutes[`${resource}:${operation}`];
34
+ if (!route) {
35
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation ${resource}:${operation}`);
36
+ }
37
+ // Optional values arrive through the route's collections; the only
38
+ // names safe to read top-level are route.topLevel — n8n resolves
39
+ // stored parameters and throws on any other name.
40
+ const values = {};
41
+ for (const name of route.collections) {
42
+ Object.assign(values, this.getNodeParameter(name, i, {}));
43
+ }
44
+ for (const name of route.topLevel) {
45
+ const v = this.getNodeParameter(name, i, null);
46
+ if (v !== null && v !== undefined)
47
+ values[name] = v;
48
+ }
49
+ await (0, transport_1.resolveBinary)(this, route, values, i);
50
+ const { path, qs, body } = (0, transport_1.buildRequest)(this.getNode(), route, values, this.getTimezone(), i);
51
+ if (route.paginated) {
52
+ const returnAll = this.getNodeParameter("returnAll", i, false);
53
+ const limit = this.getNodeParameter("limit", i, 50);
54
+ const rows = await (0, transport_1.collectPages)((cursor) => (0, transport_1.birdRequest)(this, {
55
+ method: route.method,
56
+ path,
57
+ qs: {
58
+ ...qs,
59
+ limit: returnAll ? 100 : Math.min(limit, 100),
60
+ ...(cursor ? { starting_after: cursor } : {}),
61
+ },
62
+ itemIndex: i,
63
+ }), returnAll, limit);
64
+ for (const row of rows)
65
+ out.push({ json: row, pairedItem: { item: i } });
66
+ continue;
67
+ }
68
+ // Stable within one execution run, so n8n's Retry On Fail replays
69
+ // the same key and Bird deduplicates instead of double-sending. The
70
+ // run index keeps loop iterations distinct, and the node name is
71
+ // encoded because header values must stay in Latin-1.
72
+ let runIndex = "0";
73
+ try {
74
+ runIndex = String(this.evaluateExpression("{{ $runIndex }}", i));
75
+ }
76
+ catch {
77
+ // An execution context without expressions keeps the default.
78
+ }
79
+ const headers = route.idempotencyHeader
80
+ ? {
81
+ "Idempotency-Key": `n8n/${this.getExecutionId()}/${runIndex}/${encodeURIComponent(this.getNode().name)}/${i}`.slice(0, 255),
82
+ }
83
+ : undefined;
84
+ const res = await (0, transport_1.birdRequest)(this, {
85
+ method: route.method,
86
+ path,
87
+ qs,
88
+ body,
89
+ headers,
90
+ itemIndex: i,
91
+ });
92
+ // A collection body is rows wrapped in {data: []}; n8n items are the
93
+ // rows, matching how the paginated branch already emits them.
94
+ if (route.collection) {
95
+ for (const row of res.data ?? [])
96
+ out.push({ json: row, pairedItem: { item: i } });
97
+ continue;
98
+ }
99
+ out.push({ json: res, pairedItem: { item: i } });
100
+ }
101
+ catch (error) {
102
+ if (this.continueOnFail()) {
103
+ // The remediation and the per-field details ride the description, so
104
+ // dropping it leaves the branch that keeps running with the least
105
+ // actionable half of what Bird said.
106
+ const { message, description } = error;
107
+ out.push({
108
+ json: description
109
+ ? { error: message, description }
110
+ : { error: message },
111
+ pairedItem: { item: i },
112
+ });
113
+ continue;
114
+ }
115
+ throw (0, transport_1.asNodeError)(this.getNode(), error, i);
116
+ }
117
+ }
118
+ return [out];
119
+ }
120
+ }
121
+ exports.Bird = Bird;
@@ -0,0 +1,23 @@
1
+ {
2
+ "node": "@messagebird/n8n-nodes-bird.bird",
3
+ "nodeVersion": "1.0",
4
+ "codexVersion": "1.0",
5
+ "categories": ["Communication"],
6
+ "resources": {
7
+ "primaryDocumentation": [
8
+ {
9
+ "url": "https://bird.com/docs"
10
+ }
11
+ ]
12
+ },
13
+ "alias": [
14
+ "bird",
15
+ "messagebird",
16
+ "email",
17
+ "transactional email",
18
+ "sms",
19
+ "whatsapp",
20
+ "verify",
21
+ "otp"
22
+ ]
23
+ }
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BirdTrigger = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const events_gen_1 = require("./generated/events.gen");
6
+ const transport_1 = require("./transport");
7
+ const webhook_verify_1 = require("./webhook-verify");
8
+ // Static data survives workflow restarts; endpoints are keyed by webhook URL
9
+ // because the test and production URLs subscribe as separate Bird webhooks.
10
+ function endpoints(ctx) {
11
+ const data = ctx.getWorkflowStaticData("node");
12
+ if (typeof data.endpoints !== "object" || data.endpoints === null)
13
+ data.endpoints = {};
14
+ return data.endpoints;
15
+ }
16
+ function isNotFound(error) {
17
+ return (typeof error === "object" &&
18
+ error !== null &&
19
+ error.httpCode === "404");
20
+ }
21
+ class BirdTrigger {
22
+ description = {
23
+ displayName: "Bird Trigger",
24
+ name: "birdTrigger",
25
+ icon: { light: "file:bird.svg", dark: "file:bird-dark.svg" },
26
+ group: ["trigger"],
27
+ version: 1,
28
+ subtitle: '={{ ($parameter["events"] || []).join(", ") }}',
29
+ description: "Starts a workflow when Bird delivers a subscribed event, such as an email delivery, a bounce, or an inbound SMS. Deliveries are verified as Standard Webhooks before the workflow runs.",
30
+ defaults: { name: "Bird Trigger" },
31
+ inputs: [],
32
+ outputs: [n8n_workflow_1.NodeConnectionTypes.Main],
33
+ credentials: [{ name: "birdApi", required: true }],
34
+ webhooks: [
35
+ {
36
+ name: "default",
37
+ httpMethod: "POST",
38
+ responseMode: "onReceived",
39
+ path: "default",
40
+ },
41
+ ],
42
+ properties: [
43
+ {
44
+ displayName: "Events",
45
+ name: "events",
46
+ type: "multiOptions",
47
+ required: true,
48
+ default: [],
49
+ options: events_gen_1.birdEvents,
50
+ description: "Event types this trigger subscribes to. The Bird webhook is registered automatically and receives only matching events.",
51
+ },
52
+ ],
53
+ };
54
+ webhookMethods = {
55
+ default: {
56
+ async checkExists() {
57
+ const url = this.getNodeWebhookUrl("default");
58
+ const store = endpoints(this);
59
+ const entry = store[url];
60
+ if (!entry)
61
+ return false;
62
+ try {
63
+ await (0, transport_1.birdRequest)(this, {
64
+ method: "GET",
65
+ path: `/v1/webhooks/${entry.id}`,
66
+ });
67
+ return true;
68
+ }
69
+ catch (error) {
70
+ if (isNotFound(error)) {
71
+ // Deleted out-of-band: drop the stale entry so n8n recreates.
72
+ delete store[url];
73
+ return false;
74
+ }
75
+ throw (0, transport_1.asNodeError)(this.getNode(), error);
76
+ }
77
+ },
78
+ async create() {
79
+ const url = this.getNodeWebhookUrl("default");
80
+ const events = this.getNodeParameter("events");
81
+ const res = await (0, transport_1.birdRequest)(this, {
82
+ method: "POST",
83
+ path: "/v1/webhooks",
84
+ body: { url, events },
85
+ });
86
+ const id = res.id;
87
+ const secret = res.secret;
88
+ if (!id || !secret) {
89
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), "Bird did not return a webhook id and signing secret.");
90
+ }
91
+ endpoints(this)[url] = { id, secret };
92
+ return true;
93
+ },
94
+ async delete() {
95
+ const url = this.getNodeWebhookUrl("default");
96
+ const store = endpoints(this);
97
+ const entry = store[url];
98
+ if (!entry)
99
+ return true;
100
+ try {
101
+ await (0, transport_1.birdRequest)(this, {
102
+ method: "DELETE",
103
+ path: `/v1/webhooks/${entry.id}`,
104
+ });
105
+ }
106
+ catch (error) {
107
+ if (!isNotFound(error)) {
108
+ throw (0, transport_1.asNodeError)(this.getNode(), error);
109
+ }
110
+ }
111
+ delete store[url];
112
+ return true;
113
+ },
114
+ },
115
+ };
116
+ async webhook() {
117
+ const url = this.getNodeWebhookUrl("default");
118
+ const entry = endpoints(this)[url];
119
+ const headers = this.getHeaderData();
120
+ const request = this.getRequestObject();
121
+ const rawBody = request.rawBody;
122
+ const payload = typeof rawBody === "string"
123
+ ? rawBody
124
+ : Buffer.isBuffer(rawBody)
125
+ ? rawBody.toString("utf8")
126
+ : JSON.stringify(this.getBodyData() ?? {});
127
+ const id = headers["webhook-id"] ?? "";
128
+ const timestamp = headers["webhook-timestamp"] ?? "";
129
+ const signature = headers["webhook-signature"] ?? "";
130
+ if (!entry ||
131
+ !(0, webhook_verify_1.verifyStandardWebhook)(entry.secret, id, timestamp, payload, signature)) {
132
+ this.getResponseObject()
133
+ .status(401)
134
+ .json({
135
+ error: rawBody === undefined
136
+ ? "Delivery could not be verified: the raw request body is unavailable on this n8n instance."
137
+ : "Invalid webhook signature",
138
+ });
139
+ return { noWebhookResponse: true };
140
+ }
141
+ // Bird delivers at-least-once; the signed webhook-id deduplicates. The
142
+ // redelivery is ACKED — an unanswered request would hang until timeout
143
+ // and Bird would retry the same id forever.
144
+ const data = this.getWorkflowStaticData("node");
145
+ const seen = Array.isArray(data.seenIds) ? data.seenIds : [];
146
+ if (seen.includes(id)) {
147
+ this.getResponseObject().status(200).json({ received: true });
148
+ return { noWebhookResponse: true };
149
+ }
150
+ seen.push(id);
151
+ if (seen.length > 500)
152
+ seen.splice(0, seen.length - 500);
153
+ data.seenIds = seen;
154
+ return {
155
+ workflowData: [
156
+ this.helpers.returnJsonArray([this.getBodyData()]),
157
+ ],
158
+ };
159
+ }
160
+ }
161
+ exports.BirdTrigger = BirdTrigger;
@@ -0,0 +1,23 @@
1
+ {
2
+ "node": "@messagebird/n8n-nodes-bird.birdTrigger",
3
+ "nodeVersion": "1.0",
4
+ "codexVersion": "1.0",
5
+ "categories": ["Communication"],
6
+ "resources": {
7
+ "primaryDocumentation": [
8
+ {
9
+ "url": "https://bird.com/docs"
10
+ }
11
+ ]
12
+ },
13
+ "alias": [
14
+ "bird",
15
+ "messagebird",
16
+ "webhook",
17
+ "events",
18
+ "trigger",
19
+ "email",
20
+ "sms",
21
+ "whatsapp"
22
+ ]
23
+ }
@@ -0,0 +1,4 @@
1
+ <svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="80" height="80" fill="white"/>
3
+ <path d="M58.2891 27.1368C56.5065 27.1368 54.9258 28.0817 53.9619 29.5378L47.423 39.4006C47.1848 39.7618 46.7888 40 46.3424 40C45.6073 40 45.0125 39.3595 45.0125 38.5692C45.0125 38.2742 45.093 37.9997 45.2334 37.7741L50.7563 28.8816C51.8433 27.1289 51.9458 24.8746 51.0214 23.0179C50.0985 21.1595 48.3001 20 46.3424 20H17.1406V25.7123H42.3638C42.3638 27.2867 41.1728 28.5692 39.7104 28.5692H17.1406C17.1406 30.5995 17.535 32.5304 18.2496 34.2815H37.0554C37.0554 35.8558 35.8644 37.1368 34.402 37.1368H19.794C22.2171 40.6058 26.0726 42.8506 30.4172 42.8506H38.379C39.1141 42.8506 39.7104 43.4911 39.7104 44.2815C39.7104 45.0718 39.1141 45.7123 38.379 45.7123H30.4172L21.5483 60H39.0479C46.3944 60 52.632 54.8557 54.8106 47.7378L57.8016 37.9808C58.7702 34.8131 60.5418 32.0366 62.8592 29.9306C61.9253 28.2553 60.2294 27.1368 58.2891 27.1368ZM58.2891 31.068C57.7401 31.068 57.2952 30.59 57.2952 30C57.2952 29.41 57.7401 28.9305 58.2891 28.9305C58.8412 28.9305 59.2813 29.4116 59.2813 30C59.2877 30.5869 58.8412 31.068 58.2891 31.068Z" fill="black"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="80" height="80" fill="black"/>
3
+ <path d="M58.2891 27.1368C56.5065 27.1368 54.9258 28.0818 53.9619 29.5378L47.423 39.4006C47.1848 39.7618 46.7888 40 46.3424 40C45.6073 40 45.0125 39.3596 45.0125 38.5692C45.0125 38.2742 45.093 37.9997 45.2334 37.7741L50.7563 28.8816C51.8433 27.1289 51.9458 24.8746 51.0214 23.0179C50.0985 21.1595 48.3001 20 46.3424 20H17.1406V25.7123H42.3638C42.3638 27.2867 41.1728 28.5692 39.7104 28.5692H17.1406C17.1406 30.5995 17.535 32.5304 18.2496 34.2815H37.0554C37.0554 35.8559 35.8644 37.1368 34.402 37.1368H19.794C22.2171 40.6058 26.0726 42.8507 30.4172 42.8507H38.379C39.1141 42.8507 39.7104 43.4911 39.7104 44.2815C39.7104 45.0718 39.1141 45.7123 38.379 45.7123H30.4172L21.5483 60H39.0479C46.3944 60 52.632 54.8557 54.8106 47.7379L57.8016 37.9808C58.7702 34.8131 60.5418 32.0366 62.8592 29.9306C61.9253 28.2553 60.2294 27.1368 58.2891 27.1368ZM58.2891 31.068C57.7401 31.068 57.2952 30.59 57.2952 30C57.2952 29.41 57.7401 28.9305 58.2891 28.9305C58.8412 28.9305 59.2813 29.4116 59.2813 30C59.2877 30.5869 58.8412 31.068 58.2891 31.068Z" fill="white"/>
4
+ </svg>
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.birdEvents = void 0;
4
+ // Code generated by surface-gen; DO NOT EDIT.
5
+ exports.birdEvents = [
6
+ { name: "domain.failed", value: "domain.failed", description: "A sending domain failed DNS verification" },
7
+ { name: "domain.verified", value: "domain.verified", description: "A sending domain completed DNS verification successfully" },
8
+ { name: "email.accepted", value: "email.accepted", description: "The API accepted the email send and is preparing it for delivery. Fires once per requested recipient." },
9
+ { name: "email.bounced", value: "email.bounced", description: "An outbound email permanently failed at the recipient's mail server. Fires once per recipient." },
10
+ { name: "email.canceled", value: "email.canceled", description: "A scheduled send was canceled before it fired. Fires once for each message regardless of its recipient count." },
11
+ { name: "email.clicked", value: "email.clicked", description: "The recipient clicked a tracked link in the email. May fire more than once per recipient." },
12
+ { name: "email.complained", value: "email.complained", description: "The recipient marked the email as spam through their mailbox provider's feedback loop. Fires once per recipient." },
13
+ { name: "email.deferred", value: "email.deferred", description: "The recipient's mail server temporarily refused the email. Delivery remains pending and is retried. May fire more than once per recipient." },
14
+ { name: "email.delivered", value: "email.delivered", description: "An outbound email reached the recipient's mail server and was accepted" },
15
+ { name: "email.list_unsubscribed", value: "email.list_unsubscribed", description: "Recipient unsubscribed via the RFC 8058 one-click List-Unsubscribe mechanism. Fires once per recipient." },
16
+ { name: "email.opened", value: "email.opened", description: "The recipient opened the email (the tracking pixel was loaded). May fire more than once per recipient." },
17
+ { name: "email.out_of_band_bounce", value: "email.out_of_band_bounce", description: "A bounce notification arrived after the message had already been accepted for delivery. Fires once per recipient." },
18
+ { name: "email.processed", value: "email.processed", description: "The API prepared the message for delivery to the recipient's mail server. Fires once per recipient." },
19
+ { name: "email.received", value: "email.received", description: "The API received and parsed an inbound email. The payload carries the message's identifiers, sender and recipients, subject, threading reference, and authentication results, which is enough to route and triage without a fetch. Fetch content separately with the Inbound Message operations: Body for the parsed body and Attachments for the attachment list." },
20
+ { name: "email.rejected", value: "email.rejected", description: "The API rejected the email before delivery because of suppression, transmission failure, content, or policy. Fires once per recipient." },
21
+ { name: "email.scheduled", value: "email.scheduled", description: "The API accepted an email scheduled for a future time. Fires once per message when the schedule is created." },
22
+ { name: "email.unsubscribed", value: "email.unsubscribed", description: "Recipient unsubscribed by clicking a tracked unsubscribe link in the email. Fires once per recipient." },
23
+ { name: "email_mailbox.message_delivered", value: "email_mailbox.message_delivered", description: "Every recipient of a mailbox message reached a delivered state. This event fires once per message. The same send also emits one `email.delivered` event for each recipient. Choose one event family for each automation and deduplicate mailbox events by `message_id`." },
24
+ { name: "email_mailbox.message_failed", value: "email_mailbox.message_failed", description: "A mailbox message reached a terminal delivery failure. This event fires once per message. The same send also emits per-recipient `email.*` events. Choose one event family for each automation and deduplicate mailbox events by `message_id`." },
25
+ { name: "email_mailbox.message_received", value: "email_mailbox.message_received", description: "An email arrived in a mailbox. The same message also emits an `email.received` event. The two events can arrive in either order. Choose one event family for each automation and deduplicate mailbox events by `message_id`." },
26
+ { name: "email_mailbox.message_sent", value: "email_mailbox.message_sent", description: "A mailbox message was handed off for delivery. This event fires once per message. The same send also emits per-recipient `email.*` events. Choose one event family for each automation and deduplicate mailbox events by `message_id`." },
27
+ { name: "email_mailbox.suspended", value: "email_mailbox.suspended", description: "This mailbox-suspension event is reserved and is not currently emitted" },
28
+ { name: "email_mailbox.thread_created", value: "email_mailbox.thread_created", description: "A new thread was created in a mailbox, from either direction" },
29
+ { name: "email_suppression.created", value: "email_suppression.created", description: "An email address was added to the workspace's suppression list (manually, via complaint, or via hard bounce)" },
30
+ { name: "preference.deleted", value: "preference.deleted", description: "A stated preference was deleted, superseding it in the ledger without erasing its history" },
31
+ { name: "preference.granted", value: "preference.granted", description: "A stated preference was granted (a person consented, or a customer wrote a grant) and became the key's live statement" },
32
+ { name: "preference.revoked", value: "preference.revoked", description: "A stated preference was revoked (a person opted out, or a customer wrote a revoke) and became the key's live statement" },
33
+ { name: "sms.accepted", value: "sms.accepted", description: "The API accepted the SMS send request and queued it for processing" },
34
+ { name: "sms.delivered", value: "sms.delivered", description: "The carrier confirmed delivery of the message to the recipient handset" },
35
+ { name: "sms.expired", value: "sms.expired", description: "The message's validity period elapsed before it could be delivered" },
36
+ { name: "sms.failed", value: "sms.failed", description: "Message delivery failed permanently" },
37
+ { name: "sms.received", value: "sms.received", description: "A message was received on one of your numbers" },
38
+ { name: "sms.rejected", value: "sms.rejected", description: "The API rejected the message before sending it to the carrier because of an invalid destination, suppression, or content or policy restriction" },
39
+ { name: "sms.sent", value: "sms.sent", description: "The API handed the message to the carrier for delivery" },
40
+ { name: "sms.undelivered", value: "sms.undelivered", description: "The carrier reported a non-permanent failure to deliver the message" },
41
+ { name: "sms_suppression.created", value: "sms_suppression.created", description: "A destination was added to the workspace's SMS suppression ledger: a subscriber's STOP, a carrier opt-out, or a manual add" },
42
+ { name: "verify.attempt.delivered", value: "verify.attempt.delivered", description: "The channel confirmed delivery of a one-time passcode to the recipient" },
43
+ { name: "verify.attempt.sent", value: "verify.attempt.sent", description: "A one-time passcode was dispatched to the recipient on a channel" },
44
+ { name: "verify.attempt.undelivered", value: "verify.attempt.undelivered", description: "A one-time passcode failed to deliver to the recipient" },
45
+ { name: "verify.verification.created", value: "verify.verification.created", description: "A verification session was created and its first one-time passcode is being sent" },
46
+ { name: "verify.verification.failed", value: "verify.verification.failed", description: "The verification ended without the recipient receiving a passcode: every planned channel reported that its send would not arrive" },
47
+ { name: "verify.verification.verified", value: "verify.verification.verified", description: "The verification was successfully resolved: the recipient confirmed the correct code" },
48
+ { name: "voice_call.answered", value: "voice_call.answered", description: "The called party answered and media began flowing" },
49
+ { name: "voice_call.ended", value: "voice_call.ended", description: "The call ended after either party hung up or call setup failed" },
50
+ { name: "voice_call.initiated", value: "voice_call.initiated", description: "Call routing began" },
51
+ { name: "whatsapp.accepted", value: "whatsapp.accepted", description: "The API accepted and charged the send request" },
52
+ { name: "whatsapp.delivered", value: "whatsapp.delivered", description: "The message was delivered to the recipient's device" },
53
+ { name: "whatsapp.failed", value: "whatsapp.failed", description: "Message delivery failed permanently" },
54
+ { name: "whatsapp.read", value: "whatsapp.read", description: "The recipient read the message" },
55
+ { name: "whatsapp.received", value: "whatsapp.received", description: "A contact sent the business a WhatsApp message" },
56
+ { name: "whatsapp.rejected", value: "whatsapp.rejected", description: "The API rejected the message before sending it to WhatsApp because the recipient is on the workspace suppression list, the wallet has insufficient balance, or the destination is unpriced. The message is not sent or charged." },
57
+ { name: "whatsapp.sent", value: "whatsapp.sent", description: "The API handed the message to Meta for delivery" },
58
+ { name: "whatsapp_suppression.created", value: "whatsapp_suppression.created", description: "An address was added to the workspace's WhatsApp suppression ledger" },
59
+ ];
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.birdPickers = void 0;
4
+ exports.birdPickers = {
5
+ numbersList: { route: "numbers:list", fields: ["number"], search: "", pathFrom: [] },
6
+ audiencesList: { route: "audiences:list", fields: ["name"], search: "q", pathFrom: [] },
7
+ contactsList: { route: "contacts:list", fields: ["email", "phone_number", "external_id"], search: "q", pathFrom: [] },
8
+ contactPropertiesList: { route: "contact_properties:list", fields: ["key"], search: "", pathFrom: [] },
9
+ domainsList: { route: "domains:list", fields: ["domain"], search: "name", pathFrom: [] },
10
+ emailList: { route: "email:list", fields: ["subject"], search: "", pathFrom: [] },
11
+ emailInboundRoutesList: { route: "email:inbound_routes_list", fields: ["match_value", "domain"], search: "", pathFrom: [] },
12
+ inboundAddressList: { route: "inbound_address:list", fields: ["address"], search: "", pathFrom: [] },
13
+ inboundMessageList: { route: "inbound_message:list", fields: ["subject", "message_id"], search: "", pathFrom: [] },
14
+ emailMailboxesList: { route: "email:mailboxes_list", fields: ["address"], search: "q", pathFrom: [] },
15
+ numbersOrdersList: { route: "numbers:orders_list", fields: ["number"], search: "", pathFrom: [] },
16
+ preferencesList: { route: "preferences:list", fields: ["handle"], search: "", pathFrom: [] },
17
+ realtimeAppsList: { route: "realtime:apps_list", fields: ["name"], search: "name", pathFrom: [] },
18
+ realtimeAppsKeysList: { route: "realtime:apps_keys_list", fields: ["key"], search: "", pathFrom: ["realtime_app_id"] },
19
+ emailMailboxesReceiveRulesList: { route: "email:mailboxes_receive_rules_list", fields: ["entry"], search: "", pathFrom: ["mailbox_id"] },
20
+ smsKeywordRulesList: { route: "sms_keyword_rules:list", fields: ["keywords", "number"], search: "", pathFrom: [] },
21
+ smsList: { route: "sms:list", fields: ["to"], search: "", pathFrom: [] },
22
+ smsSuppressionsList: { route: "sms_suppressions:list", fields: ["destination"], search: "", pathFrom: [] },
23
+ suppressionList: { route: "suppression:list", fields: ["email"], search: "email", pathFrom: [] },
24
+ emailThreadsList: { route: "email:threads_list", fields: ["subject"], search: "subject", pathFrom: [] },
25
+ voiceList: { route: "voice:list", fields: ["to"], search: "", pathFrom: [] },
26
+ webhooksList: { route: "webhooks:list", fields: ["url"], search: "", pathFrom: [] },
27
+ whatsappList: { route: "whatsapp:list", fields: ["to.phone_number", "to.bsuid", "created_at"], search: "", pathFrom: [] },
28
+ };