ListMonk management
ListMonk Subflow
This Node-RED subflow provides an interface to interact with a ListMonk mailing list server. It allows you to perform various operations, such as retrieving subscribers, managing subscriptions, adding/deleting subscribers, and managing campaigns. The subflow uses the ListMonk REST API.
Table of Contents
Configuration
The subflow requires the following configuration parameters, which can be set as environment variables within the subflow's configuration:
Parameter | Type | Description |
---|---|---|
LISTMONK_BASE_URL |
str | The base URL of your ListMonk instance (e.g., https://your-listmonk-instance.com/api ). Required. |
LISTMONK_API_USER |
str | The username for your ListMonk API user. Required. |
LISTMONK_API_KEY |
str | The API key for your ListMonk API user. Required. |
COMMAND |
str | The default command to execute. This can be overridden by the msg.topic of the incoming message. See Commands for available options. Defaults to getting the command from msg.topic . |
TEST_EMAILS |
json | An array of email addresses to use for testing campaigns. Required for the test_campaign command. This should be a JSON array of strings (e.g., ["[email protected]", "[email protected]"] ). Defaults to an empty array ([] ). |
These parameters are used to construct the API requests to your ListMonk server. Without these, the subflow will not function correctly. You can use either string values directly in the subflow configuration or the names of global environment variables.
Inputs
The subflow accepts a single input, which is a standard Node-RED message object (msg
). The key property is msg.topic
, which determines the action to be performed. msg.payload
is used to pass data to the specific command.
msg.topic
(string): The command to execute. This overrides theCOMMAND
variable. See the Commands section for a list of valid commands. Ifmsg.topic
is not provided (or is empty), and the subflow'sCOMMAND
variable is set to "topic", then no action is taken.msg.payload
(various): The data required by the specific command. The format ofmsg.payload
varies depending on the command. See the documentation for each command below.
Outputs
The subflow has a single output. The output message (msg
) will contain the following:
msg.payload
(object or array): The response from the ListMonk API. The structure of this data depends on the command executed. See the ListMonk API documentation for details on the response formats. Forget_subscriptions_by_userid
, the subflow extracts thelists
array from the response data and sets it asmsg.payload
.msg.statusCode
(number): The HTTP status code of the API request. A 200 status code generally indicates success.msg.headers
(object): The HTTP headers returned by the ListMonk API.msg.topic
: The command that was executed.
Commands
The following commands are supported by the subflow. These commands correspond to endpoints in the ListMonk API. The commands are grouped into Subscriber Management and Campaign Management.
Subscriber Management
get_subscribers
Retrieves all subscribers from ListMonk.
- Input (
msg.payload
): Not used. - Output (
msg.payload
): An array of subscriber objects. See the ListMonk API documentation for the structure of the subscriber object. - Example: Send a message with
msg.topic = "get_subscribers"
.
get_subscriber_by_id
Retrieves a single subscriber by their ID.
- Input (
msg.payload
): (number) The ID of the subscriber to retrieve. - Output (
msg.payload
): A single subscriber object. See the ListMonk API documentation. - Example: Send a message with
msg.topic = "get_subscriber_by_id"
andmsg.payload = 123
(where 123 is the subscriber ID).
get_subscriptions_by_userid
Retrieves the subscriptions for a specific user ID. This uses the same Listmonk API endpoint as get_subscriber_by_id
. The subflow extracts the relevant part of the result.
- Input (
msg.payload
): (number) The ID of the subscriber. - Output (
msg.payload
): An array of list objects representing the subscriber's subscriptions. The subflow transforms the raw API response to only include thelists
data. - Example: Send a message with
msg.topic = "get_subscriptions_by_userid"
andmsg.payload = 123
.
add_subscription
Adds a subscriber to a specific list.
- Input (
msg.payload
): (object) An object with the following properties:user
(number): The ID of the subscriber.list
(number): The ID of the list to subscribe the user to.
- Output (
msg.payload
): The API response from ListMonk. See the ListMonk API documentation. - Example: Send a message with
msg.topic = "add_subscription"
andmsg.payload = { user: 123, list: 456 }
.
remove_subscription
Removes a subscriber from a specific list.
- Input (
msg.payload
): (object) An object with the following properties:user
(number): The ID of the subscriber.list
(number): The ID of the list to unsubscribe the user from.
- Output (
msg.payload
): The API response from ListMonk. - Example: Send a message with
msg.topic = "remove_subscription"
andmsg.payload = { user: 123, list: 456 }
.
unsubscribe
Unsubscribes a user from a specific list. This is functionally identical to remove_subscription
in this subflow.
- Input (
msg.payload
): (object) An object with the following properties:user
(number): The ID of the subscriber.list
(number): The ID of the list to unsubscribe the user from.
- Output (
msg.payload
): The API response from ListMonk. - Example: Send a message with
msg.topic = "unsubscribe"
andmsg.payload = { user: 123, list: 456 }
.
add_subscriber
Adds a new subscriber to ListMonk.
- Input (
msg.payload
): (object) A subscriber object. See the ListMonk API documentation for the required fields. At a minimum, you'll likely needemail
,name
, andlists
(an array of list IDs). The subflow doesn't perform any validation of this object. - Output (
msg.payload
): The API response from ListMonk, which will include the newly created subscriber object. - Example: Send a message with:
{ "topic": "add_subscriber", "payload": { "email": "[email protected]", "name": "Test User", "lists": [1], "attribs": { "city": "Anytown" }, "preconfirm_subscriptions": true } }
get_subscriber_by_attrib
Retrieves subscribers based on a custom attribute.
- Input (
msg.payload
): (array) A two-element array containing:[0]
(string): The name of the custom attribute (e.g., "internalUserId"). This attribute must be defined in your ListMonk subscriber attributes.[1]
(string or number): The value of the attribute to search for.
- Output (
msg.payload
): An array of subscribers that match the query. - Example: Send a message with
msg.topic = "get_subscriber_by_attrib"
andmsg.payload = ["internalUserId", "1234567890"]
.
delete_subscriber_by_attrib
Deletes subscribers based on custom attributes.
- Input (
msg.payload
): (array) A two-element array containing:[0]
(string): The name of the custom attribute (e.g., "internalUserId").[1]
(string or number): The value of the attribute.
- Output (
msg.payload
): The API response from ListMonk. - Example: Send a message with
msg.topic = "delete_subscriber_by_attrib"
andmsg.payload = ["internalUserId", "1234567890"]
.
Campaign Management
create_campaign
Creates a new campaign in ListMonk.
- Input (
msg.payload
): (object) A campaign object. See the ListMonk API documentation for the available fields. The following fields are commonly used:name
(string): The name of the campaign.subject
(string): The subject line of the campaign.lists
(array): An array of list IDs to send the campaign to.body
(string): The HTML body of the campaign.from_email
(string): The "from" email address.type
(string): The campaign type (regular
oroptin
).content_type
(string):html
orplain
.template_id
(number, optional): The ID of a template to use.send_at
(string, optional): Date and time for schedule a campaign.tags
(array, optional): An array of tags.
- Output (
msg.payload
): The API response from ListMonk, including the newly created campaign object. - Example:
{ "topic": "create_campaign", "payload": { "name": "My Campaign", "subject": "My Newsletter", "lists": [1, 2], "body": "<html><body><h1>Hello!</h1></body></html>", "from_email": "[email protected]", "type": "regular", "content_type": "html", "tags": ["newsletter", "promotional"] } }
schedule_campaign
Schedules a campaign to be sent. This changes the campaign's status to "scheduled".
- Input (
msg.payload
): (object) an object containing adata
field. Inside must containid
field.id
(number): The ID of the campaign to schedule.
- Output (
msg.payload
): The API response from ListMonk. - Example:
{ "topic": "schedule_campaign", "payload": { "data": { "id": 75 } } }
test_campaign
Sends a test email for a campaign. Requires the TEST_EMAILS
variable to be set.
Input (
msg.payload
): (object) An object with adata
property, which is itself an object containing campaign data. It is normally the output of Create campaign command.Output (
msg.payload
): The API response from ListMonk.Example:
{
"topic": "test_campaign",
"payload": {
"data": {
"id": 70
}
}
}
- Notes:
- The TEST_EMAILS variable must be a JSON array of email addresses. The test email will be sent to all addresses in this array.
- The subject of the test email will have (test) appended to the campaign's original subject.
start_campaign
Starts a campaign. This changes the campaign's status to "running".
Input (
msg.payload
): (object) an object containing a data field. Inside must containid
field.- id (number): The ID of the campaign to schedule.
Output (
msg.payload
): The API response from ListMonk.Example:
{
"topic": "start_campaign",
"payload": {
"data": {
"id": 75
}
}
}
Transactional Emails
transactional_send
Sends a transactional email via ListMonk. Transactional emails are typically used for sending things like password reset emails, welcome emails, or purchase confirmations.
Input (
msg.payload
): (object) An object containing the following properties:subscriber_email
(string): The email address of the recipient.template_id
(number): The ID of the template to use for the email. It must be a transactional template.content_type
(string): The content type of the email (html
orplain
).data
(object): An object containing the data to be used to populate the template. See the ListMonk documentation for details about the format.
Output (
msg.payload
): The API response from ListMonk.Example:
{ "topic": "transactional_send", "payload": { "subscriber_email": "[email protected]", "template_id": 12, "content_type": "html", "data": { "user": "username123", "confirm_link": "https://example.com/confirm" } } }
Error Handling
- If the LISTMONK_BASE_URL, LISTMONK_API_USER, or LISTMONK_API_KEY variables are not set, the subflow will log an error to the Node-RED console using node.error(). The flow will not proceed.
- The subflow uses an http request node. Any errors from this node (e.g., network errors, invalid responses) will be passed through to the output of the subflow. The msg.statusCode will reflect the HTTP error code.
- If an unknown command is used and the switch node reaches the "unknown command" function node, the status of the subflow node will change to a red dot with the text "unknown: command". An error message will also be sent to the Node-RED debug pane.
- If the id campaign is missing on schedule_campaign, test_campaign or start_campaign commands, the subflow will log an error to the Node-RED console using node.error().
Example Usage
The provided flow includes several inject nodes that demonstrate how to use the subflow with different commands. You can import this flow into Node-RED and modify the inject nodes to test the subflow with your ListMonk instance. Remember to set the LISTMONK_BASE_URL, LISTMONK_API_USER, LISTMONK_API_KEY, and TEST_EMAILS variables in the subflow configuration.
[{"category": "", "color": "#0055d4", "env": [{"name": "LISTMONK_BASE_URL", "type": "str", "ui": {"label": {"en-US": "Base URL"}, "opts": {"types": ["str", "env"]}, "type": "input"}, "value": "https://my.listmonk.instance/api"}, {"name": "LISTMONK_API_USER", "type": "str", "ui": {"label": {"en-US": "Api user"}, "opts": {"types": ["str", "env"]}, "type": "input"}, "value": ""}, {"name": "LISTMONK_API_KEY", "type": "str", "ui": {"label": {"en-US": "API key"}, "opts": {"types": ["str", "env"]}, "type": "input"}, "value": ""}, {"name": "COMMAND", "type": "str", "ui": {"label": {"en-US": "Command"}, "opts": {"opts": [{"l": {"en-US": "from msg.topic"}, "v": "topic"}, {"l": {"en-US": "get subscribers"}, "v": "get_subscribers"}, {"l": {"en-US": "get subscriber by id"}, "v": "get_subscriber_by_id"}, {"l": {"en-US": "get subscriptions by userid"}, "v": "get_subscriptions_by_userid"}, {"l": {"en-US": "subscription add"}, "v": "add_subscription"}, {"l": {"en-US": "subscription remove"}, "v": "remove_subscription"}, {"l": {"en-US": "subscription unsubscribe"}, "v": "unsubscribe"}, {"l": {"en-US": "subscriber add"}, "v": "add_subscriber"}, {"l": {"en-US": "subscriber get by attrib"}, "v": "get_subscriber_by_attrib"}, {"l": {"en-US": "subscriber delete by attrib"}, "v": "delete_subscriber_by_attrib"}, {"l": {"en-US": "campaign create"}, "v": "create_campaign"}, {"l": {"en-US": "campaign schedule"}, "v": "schedule_campaign"}, {"l": {"en-US": "campaign test"}, "v": "test_campaign"}, {"l": {"en-US": "campaign start"}, "v": "start_campaign"}, {"l": {"en-US": "send email"}, "v": "transactional_send"}]}, "type": "select"}, "value": "topic"}, {"name": "TEST_EMAILS", "type": "json", "ui": {"label": {"en-US": "Test email addresses"}, "opts": {"types": ["json", "env"]}, "type": "input"}, "value": "[]"}], "icon": "node-red/envelope.svg", "id": "539a548b549c19dd", "in": [{"wires": [{"id": "e4d6a43404fcd1aa"}], "x": 80, "y": 400}], "info": "# ListMonk Subflow\r\n\r\nThis Node-RED subflow provides an interface to interact with a [ListMonk](https://listmonk.app/) mailing list server. It allows you to perform various operations, such as retrieving subscribers, managing subscriptions, adding/deleting subscribers, and managing campaigns. The subflow uses the ListMonk REST API.\r\n\r\n## Table of Contents\r\n\r\n- [Configuration](#configuration)\r\n- [Inputs](#inputs)\r\n- [Outputs](#outputs)\r\n- [Commands](#commands)\r\n - [Subscriber Management](#subscriber-management)\r\n - [get_subscribers](#get_subscribers)\r\n - [get_subscriber_by_id](#get_subscriber_by_id)\r\n - [get_subscriptions_by_userid](#get_subscriptions_by_userid)\r\n - [add_subscription](#add_subscription)\r\n - [remove_subscription](#remove_subscription)\r\n - [unsubscribe](#unsubscribe)\r\n - [add_subscriber](#add_subscriber)\r\n - [get_subscriber_by_attrib](#get_subscriber_by_attrib)\r\n - [delete_subscriber_by_attrib](#delete_subscriber_by_attrib)\r\n - [Campaign Management](#campaign-management)\r\n - [create_campaign](#create_campaign)\r\n - [schedule_campaign](#schedule_campaign)\r\n - [test_campaign](#test_campaign)\r\n - [start_campaign](#start_campaign)\r\n- [Error Handling](#error-handling)\r\n- [Example Usage](#example-usage)\r\n\r\n## Configuration\r\n\r\nThe subflow requires the following configuration parameters, which can be set as environment variables within the subflow's configuration:\r\n\r\n| Parameter | Type | Description |\r\n| ------------------- | ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\r\n| `LISTMONK_BASE_URL` | str | The base URL of your ListMonk instance (e.g., `https://your-listmonk-instance.com/api`). **Required.** |\r\n| `LISTMONK_API_USER` | str | The username for your ListMonk API user. **Required.** |\r\n| `LISTMONK_API_KEY` | str | The API key for your ListMonk API user. **Required.** |\r\n| `COMMAND` | str | The default command to execute. This can be overridden by the `msg.topic` of the incoming message. See [Commands](#commands) for available options. Defaults to getting the command from `msg.topic`. |\r\n| `TEST_EMAILS` | json | An array of email addresses to use for testing campaigns. **Required for the `test_campaign` command.** This should be a JSON array of strings (e.g., `[\"[email protected]\", \"[email protected]\"]`). Defaults to an empty array (`[]`). |\r\n\r\nThese parameters are used to construct the API requests to your ListMonk server. Without these, the subflow will not function correctly. You can use either string values directly in the subflow configuration or the names of global environment variables.\r\n\r\n## Inputs\r\n\r\nThe subflow accepts a single input, which is a standard Node-RED message object (`msg`). The key property is `msg.topic`, which determines the action to be performed. `msg.payload` is used to pass data to the specific command.\r\n\r\n- **`msg.topic`** (string): The command to execute. This *overrides* the `COMMAND` variable. See the [Commands](#commands) section for a list of valid commands. If `msg.topic` is not provided (or is empty), and the subflow's `COMMAND` variable is set to \"topic\", then no action is taken.\r\n- **`msg.payload`** (various): The data required by the specific command. The format of `msg.payload` varies depending on the command. See the documentation for each command below.\r\n\r\n## Outputs\r\n\r\nThe subflow has a single output. The output message (`msg`) will contain the following:\r\n\r\n- **`msg.payload`** (object or array): The response from the ListMonk API. The structure of this data depends on the command executed. See the ListMonk API documentation for details on the response formats. For `get_subscriptions_by_userid`, the subflow extracts the `lists` array from the response data and sets it as `msg.payload`.\r\n- **`msg.statusCode`** (number): The HTTP status code of the API request. A 200 status code generally indicates success.\r\n- **`msg.headers`** (object): The HTTP headers returned by the ListMonk API.\r\n- **`msg.topic`**: The command that was executed.\r\n\r\n## Commands\r\n\r\nThe following commands are supported by the subflow. These commands correspond to endpoints in the ListMonk API. The commands are grouped into Subscriber Management and Campaign Management.\r\n\r\n### Subscriber Management\r\n\r\n#### `get_subscribers`\r\n\r\nRetrieves all subscribers from ListMonk.\r\n\r\n- **Input (`msg.payload`):** Not used.\r\n- **Output (`msg.payload`):** An array of subscriber objects. See the [ListMonk API documentation](https://listmonk.app/docs/apis/subscribers/#get-apisubscribers) for the structure of the subscriber object.\r\n- **Example:** Send a message with `msg.topic = \"get_subscribers\"`.\r\n\r\n#### `get_subscriber_by_id`\r\n\r\nRetrieves a single subscriber by their ID.\r\n\r\n- **Input (`msg.payload`):** (number) The ID of the subscriber to retrieve.\r\n- **Output (`msg.payload`):** A single subscriber object. See the [ListMonk API documentation](https://listmonk.app/docs/apis/subscribers/#get-apisubscriberssubscriber_id).\r\n- **Example:** Send a message with `msg.topic = \"get_subscriber_by_id\"` and `msg.payload = 123` (where 123 is the subscriber ID).\r\n\r\n#### `get_subscriptions_by_userid`\r\n\r\nRetrieves the subscriptions for a specific user ID. This uses the same Listmonk API endpoint as `get_subscriber_by_id`. The subflow extracts the relevant part of the result.\r\n\r\n- **Input (`msg.payload`):** (number) The ID of the subscriber.\r\n- **Output (`msg.payload`):** An array of list objects representing the subscriber's subscriptions. The subflow transforms the raw API response to only include the `lists` data.\r\n- **Example:** Send a message with `msg.topic = \"get_subscriptions_by_userid\"` and `msg.payload = 123`.\r\n\r\n#### `add_subscription`\r\n\r\nAdds a subscriber to a specific list.\r\n\r\n- **Input (`msg.payload`):** (object) An object with the following properties:\r\n - `user` (number): The ID of the subscriber.\r\n - `list` (number): The ID of the list to subscribe the user to.\r\n- **Output (`msg.payload`):** The API response from ListMonk. See the [ListMonk API documentation](https://listmonk.app/docs/apis/subscribers/#put-apisubscriberslists).\r\n- **Example:** Send a message with `msg.topic = \"add_subscription\"` and `msg.payload = { user: 123, list: 456 }`.\r\n\r\n#### `remove_subscription`\r\n\r\nRemoves a subscriber from a specific list.\r\n\r\n- **Input (`msg.payload`):** (object) An object with the following properties:\r\n - `user` (number): The ID of the subscriber.\r\n - `list` (number): The ID of the list to unsubscribe the user from.\r\n- **Output (`msg.payload`):** The API response from ListMonk.\r\n- **Example:** Send a message with `msg.topic = \"remove_subscription\"` and `msg.payload = { user: 123, list: 456 }`.\r\n\r\n#### `unsubscribe`\r\n\r\nUnsubscribes a user from a specific list. This is functionally identical to `remove_subscription` in this subflow.\r\n\r\n- **Input (`msg.payload`):** (object) An object with the following properties:\r\n - `user` (number): The ID of the subscriber.\r\n - `list` (number): The ID of the list to unsubscribe the user from.\r\n- **Output (`msg.payload`):** The API response from ListMonk.\r\n- **Example:** Send a message with `msg.topic = \"unsubscribe\"` and `msg.payload = { user: 123, list: 456 }`.\r\n\r\n#### `add_subscriber`\r\n\r\nAdds a new subscriber to ListMonk.\r\n\r\n- **Input (`msg.payload`):** (object) A subscriber object. See the [ListMonk API documentation](https://listmonk.app/docs/apis/subscribers/#post-apisubscribers) for the required fields. At a minimum, you'll likely need `email`, `name`, and `lists` (an array of list IDs). The subflow doesn't perform any validation of this object.\r\n- **Output (`msg.payload`):** The API response from ListMonk, which will include the newly created subscriber object.\r\n- **Example:** Send a message with:\r\n ```json\r\n {\r\n \"topic\": \"add_subscriber\",\r\n \"payload\": {\r\n \"email\": \"[email protected]\",\r\n \"name\": \"Test User\",\r\n \"lists\": [1],\r\n \"attribs\": {\r\n \"city\": \"Anytown\"\r\n },\r\n \"preconfirm_subscriptions\": true\r\n }\r\n }\r\n ```\r\n\r\n#### `get_subscriber_by_attrib`\r\n\r\nRetrieves subscribers based on a custom attribute.\r\n\r\n- **Input (`msg.payload`):** (array) A two-element array containing:\r\n - `[0]` (string): The name of the custom attribute (e.g., \"internalUserId\"). This attribute must be defined in your ListMonk subscriber attributes.\r\n - `[1]` (string or number): The value of the attribute to search for.\r\n- **Output (`msg.payload`):** An array of subscribers that match the query.\r\n- **Example:** Send a message with `msg.topic = \"get_subscriber_by_attrib\"` and `msg.payload = [\"internalUserId\", \"1234567890\"]`.\r\n\r\n#### `delete_subscriber_by_attrib`\r\n\r\nDeletes subscribers based on custom attributes.\r\n\r\n- **Input (`msg.payload`):** (array) A two-element array containing:\r\n - `[0]` (string): The name of the custom attribute (e.g., \"internalUserId\").\r\n - `[1]` (string or number): The value of the attribute.\r\n- **Output (`msg.payload`):** The API response from ListMonk.\r\n- **Example:** Send a message with `msg.topic = \"delete_subscriber_by_attrib\"` and `msg.payload = [\"internalUserId\", \"1234567890\"]`.\r\n\r\n### Campaign Management\r\n\r\n#### `create_campaign`\r\n\r\nCreates a new campaign in ListMonk.\r\n\r\n- **Input (`msg.payload`):** (object) A campaign object. See the [ListMonk API documentation](https://listmonk.app/docs/apis/campaigns/#post-apicampaigns) for the available fields. The following fields are commonly used:\r\n - `name` (string): The name of the campaign.\r\n - `subject` (string): The subject line of the campaign.\r\n - `lists` (array): An array of list IDs to send the campaign to.\r\n - `body` (string): The HTML body of the campaign.\r\n - `from_email` (string): The \"from\" email address.\r\n - `type` (string): The campaign type (`regular` or `optin`).\r\n - `content_type` (string): `html` or `plain`.\r\n - `template_id` (number, optional): The ID of a template to use.\r\n - `send_at` (string, optional): Date and time for schedule a campaign.\r\n - `tags` (array, optional): An array of tags.\r\n- **Output (`msg.payload`):** The API response from ListMonk, including the newly created campaign object.\r\n- **Example:**\r\n ```json\r\n {\r\n \"topic\": \"create_campaign\",\r\n \"payload\": {\r\n \"name\": \"My Campaign\",\r\n \"subject\": \"My Newsletter\",\r\n \"lists\": [1, 2],\r\n \"body\": \"<html><body><h1>Hello!</h1></body></html>\",\r\n \"from_email\": \"[email protected]\",\r\n \"type\": \"regular\",\r\n \"content_type\": \"html\",\r\n \"tags\": [\"newsletter\", \"promotional\"]\r\n }\r\n }\r\n ```\r\n\r\n#### `schedule_campaign`\r\n\r\nSchedules a campaign to be sent. This changes the campaign's status to \"scheduled\".\r\n\r\n- **Input (`msg.payload`):** (object) an object containing a `data` field. Inside must contain `id` field.\r\n - `id` (number): The ID of the campaign to schedule.\r\n- **Output (`msg.payload`):** The API response from ListMonk.\r\n- **Example:**\r\n ```json\r\n {\r\n \"topic\": \"schedule_campaign\",\r\n \"payload\": {\r\n \"data\": {\r\n \"id\": 75\r\n }\r\n }\r\n }\r\n ```\r\n\r\n#### `test_campaign`\r\n\r\nSends a test email for a campaign. **Requires the `TEST_EMAILS` variable to be set.**\r\n\r\n- **Input (`msg.payload`):** (object) An object with a `data` property, which is itself an object containing campaign data. It is normally the output of [Create campaign command](#create_campaign)\r\n\r\n- **Output (`msg.payload`):** The API response from ListMonk.\r\n\r\n- **Example:**\r\n```json\r\n{\r\n \"topic\": \"test_campaign\",\r\n \"payload\": {\r\n \"data\": {\r\n \"id\": 70\r\n }\r\n }\r\n}\r\n```\r\n\r\n- Notes:\r\n\t- The TEST_EMAILS variable must be a JSON array of email addresses. The test email will be sent to all addresses in this array.\r\n\t- The subject of the test email will have (test) appended to the campaign's original subject.\r\n\r\n#### `start_campaign`\r\n\r\nStarts a campaign. This changes the campaign's status to \"running\".\r\n\r\n- **Input (`msg.payload`):** (object) an object containing a data field. Inside must contain `id` field.\r\n\t- **id (number):** The ID of the campaign to schedule.\r\n\r\n- **Output (`msg.payload`):** The API response from ListMonk.\r\n\r\n- Example:\r\n\r\n```json\r\n{\r\n \"topic\": \"start_campaign\",\r\n \"payload\": {\r\n \"data\": {\r\n \"id\": 75\r\n }\r\n }\r\n}\r\n```\r\n\r\n## Error Handling\r\n\r\n- If the LISTMONK_BASE_URL, LISTMONK_API_USER, or LISTMONK_API_KEY variables are not set, the subflow will log an error to the Node-RED console using node.error(). The flow will not proceed.\r\n- The subflow uses an http request node. Any errors from this node (e.g., network errors, invalid responses) will be passed through to the output of the subflow. The msg.statusCode will reflect the HTTP error code.\r\n- If an unknown command is used and the switch node reaches the \"unknown command\" function node, the status of the subflow node will change to a red dot with the text \"unknown: command\". An error message will also be sent to the Node-RED debug pane.\r\n- If the id campaign is missing on schedule_campaign, test_campaign or start_campaign commands, the subflow will log an error to the Node-RED console using node.error().\r\n\r\n## Example Usage\r\n\r\nThe provided flow includes several inject nodes that demonstrate how to use the subflow with different commands. You can import this flow into Node-RED and modify the inject nodes to test the subflow with your ListMonk instance. Remember to set the LISTMONK_BASE_URL, LISTMONK_API_USER, LISTMONK_API_KEY, and TEST_EMAILS variables in the subflow configuration.\r\n\r\n```json\r\n[{\"env\": [{\"name\": \"LISTMONK_API_USER\", \"type\": \"str\", \"value\": \"my_api_user\"}, {\"name\": \"LISTMONK_API_KEY\", \"type\": \"str\", \"value\": \"xxxxxxxxxxxxxxxxx\"}], \"id\": \"a695d7033f713e4a\", \"name\": \"\", \"type\": \"subflow:539a548b549c19dd\", \"wires\": [[\"bc6df03166084a21\"]], \"x\": 1500, \"y\": 3100, \"z\": \"02bb203de5d55ce5\"}, {\"crontab\": \"\", \"id\": \"75822a732d8d77c2\", \"name\": \"\", \"once\": false, \"onceDelay\": 0.1, \"payload\": \"\", \"payloadType\": \"str\", \"props\": [{\"p\": \"payload\"}, {\"p\": \"topic\", \"vt\": \"str\"}], \"repeat\": \"\", \"topic\": \"get_subscribers\", \"type\": \"inject\", \"wires\": [[\"a695d7033f713e4a\"]], \"x\": 1140, \"y\": 2940, \"z\": \"02bb203de5d55ce5\"}, {\"crontab\": \"\", \"id\": \"2d26b96d9a2cbc8d\", \"name\": \"add_subscription\", \"once\": false, \"onceDelay\": 0.1, \"payload\": \"{\\\"user\\\":3,\\\"list\\\":4}\", \"payloadType\": \"json\", \"props\": [{\"p\": \"payload\"}, {\"p\": \"topic\", \"vt\": \"str\"}], \"repeat\": \"\", \"topic\": \"add_subscription\", \"type\": \"inject\", \"wires\": [[\"a695d7033f713e4a\"]], \"x\": 1080, \"y\": 3100, \"z\": \"02bb203de5d55ce5\"}, {\"crontab\": \"\", \"id\": \"0d034144e9fad648\", \"name\": \"remove_subscription\", \"once\": false, \"onceDelay\": 0.1, \"payload\": \"{\\\"user\\\":3,\\\"list\\\":4}\", \"payloadType\": \"json\", \"props\": [{\"p\": \"payload\"}, {\"p\": \"topic\", \"vt\": \"str\"}], \"repeat\": \"\", \"topic\": \"remove_subscription\", \"type\": \"inject\", \"wires\": [[\"a695d7033f713e4a\"]], \"x\": 1070, \"y\": 3140, \"z\": \"02bb203de5d55ce5\"}, {\"crontab\": \"\", \"id\": \"9c1707deb912fcf8\", \"name\": \"get_subscriber_by_attrib\", \"once\": false, \"onceDelay\": 0.1, \"payload\": \"[\\\"internal_user\\\",1234567890]\", \"payloadType\": \"json\", \"props\": [{\"p\": \"payload\"}, {\"p\": \"topic\", \"vt\": \"str\"}], \"repeat\": \"\", \"topic\": \"get_subscriber_by_attrib\", \"type\": \"inject\", \"wires\": [[\"a695d7033f713e4a\"]], \"x\": 1110, \"y\": 3260, \"z\": \"02bb203de5d55ce5\"}, {\"crontab\": \"\", \"id\": \"3d0cec56c5b275c8\", \"name\": \"delete_subscriber_by_attrib\", \"once\": false, \"onceDelay\": 0.1, \"payload\": \"[\\\"internal_user\\\",1234567890]\", \"payloadType\": \"json\", \"props\": [{\"p\": \"payload\"}, {\"p\": \"topic\", \"vt\": \"str\"}], \"repeat\": \"\", \"topic\": \"delete_subscriber_by_attrib\", \"type\": \"inject\", \"wires\": [[\"a695d7033f713e4a\"]], \"x\": 1100, \"y\": 3300, \"z\": \"02bb203de5d55ce5\"}, {\"crontab\": \"\", \"id\": \"47f34392c4c4b923\", \"name\": \"add_subscriber\", \"once\": false, \"onceDelay\": 0.1, \"payload\": \"{\\\"email\\\":\\\"[email protected]\\\",\\\"name\\\":\\\"test4\\\",\\\"status\\\":\\\"enabled\\\",\\\"lists\\\":[2],\\\"attribs\\\":{\\\"internal_user\\\":\\\"1234567890\\\"},\\\"preconfirm_subscriptions\\\":true}\", \"payloadType\": \"json\", \"props\": [{\"p\": \"payload\"}, {\"p\": \"topic\", \"vt\": \"str\"}], \"repeat\": \"\", \"topic\": \"add_subscriber\", \"type\": \"inject\", \"wires\": [[\"a695d7033f713e4a\"]], \"x\": 1140, \"y\": 3220, \"z\": \"02bb203de5d55ce5\"}, {\"active\": true, \"complete\": \"false\", \"console\": false, \"id\": \"bc6df03166084a21\", \"name\": \"debug 171\", \"statusType\": \"auto\", \"statusVal\": \"\", \"tosidebar\": true, \"tostatus\": false, \"type\": \"debug\", \"wires\": [], \"x\": 1690, \"y\": 3100, \"z\": \"02bb203de5d55ce5\"}, {\"crontab\": \"\", \"id\": \"420850a07614dd09\", \"name\": \"create scheduled campaign\", \"once\": false, \"onceDelay\": 0.1, \"payload\": \"{\\\"name\\\":\\\"Daily Newsletter\\\",\\\"subject\\\":\\\"My news letter\\\",\\\"lists\\\":[1],\\\"body\\\":\\\"<div class='section-title'><h2>Test</h2></div>\\\",\\\"from_email\\\":\\\"Sender <[email protected]>\\\",\\\"type\\\":\\\"regular\\\",\\\"content_type\\\":\\\"html\\\",\\\"template_id\\\":1,\\\"tags\\\":[\\\"maillist\\\"],\\\"send_at\\\":\\\"2025-03-25T10:00:00Z\\\"}\", \"payloadType\": \"json\", \"props\": [{\"p\": \"payload\"}, {\"p\": \"topic\", \"vt\": \"str\"}], \"repeat\": \"\", \"topic\": \"create_campaign\", \"type\": \"inject\", \"wires\": [[\"f6ab457865b2e961\"]], \"x\": 1140, \"y\": 3400, \"z\": \"02bb203de5d55ce5\"}, {\"env\": [{\"name\": \"LISTMONK_API_USER\", \"type\": \"str\", \"value\": \"user\"}, {\"name\": \"LISTMONK_API_KEY\", \"type\": \"env\", \"value\": \"xxxxxxxxxxxxxxxxx\"}, {\"name\": \"COMMAND\", \"type\": \"str\", \"value\": \"create_campaign\"}], \"id\": \"f6ab457865b2e961\", \"name\": \"Create\", \"type\": \"subflow:539a548b549c19dd\", \"wires\": [[\"7822385efcf41610\", \"9757ed4f630165fa\", \"f175cf628d7ef044\"]], \"x\": 1330, \"y\": 3400, \"z\": \"02bb203de5d55ce5\"}, {\"active\": true, \"complete\": \"false\", \"console\": false, \"id\": \"18dbb5da307169cc\", \"name\": \"debug 176\", \"statusType\": \"auto\", \"statusVal\": \"\", \"tosidebar\": true, \"tostatus\": false, \"type\": \"debug\", \"wires\": [], \"x\": 1687, \"y\": 3379, \"z\": \"02bb203de5d55ce5\"}, {\"active\": true, \"complete\": \"false\", \"console\": false, \"id\": \"bfb80a88ec3011ed\", \"name\": \"debug 177\", \"statusType\": \"auto\", \"statusVal\": \"\", \"tosidebar\": true, \"tostatus\": false, \"type\": \"debug\", \"wires\": [], \"x\": 1690, \"y\": 3440, \"z\": \"02bb203de5d55ce5\"}, {\"crontab\": \"\", \"id\": \"5fc9aea7b927d50a\", \"name\": \"create and start campaign\", \"once\": false, \"onceDelay\": 0.1, \"payload\": \"{\\\"name\\\":\\\"Daily Newsletter\\\",\\\"subject\\\":\\\"My news letter\\\",\\\"lists\\\":[1],\\\"body\\\":\\\"<div class='section-title'><h2>Test</h2></div>\\\",\\\"from_email\\\":\\\"Sender <[email protected]>\\\",\\\"type\\\":\\\"regular\\\",\\\"content_type\\\":\\\"html\\\",\\\"template_id\\\":1,\\\"tags\\\":[\\\"maillist\\\"]}\", \"payloadType\": \"json\", \"props\": [{\"p\": \"payload\"}, {\"p\": \"topic\", \"vt\": \"str\"}], \"repeat\": \"\", \"topic\": \"start_campaign\", \"type\": \"inject\", \"wires\": [[\"c4cf33d1546ce804\"]], \"x\": 1110, \"y\": 3540, \"z\": \"02bb203de5d55ce5\"}, {\"active\": true, \"complete\": \"false\", \"console\": false, \"id\": \"46faf6fb1e31620c\", \"name\": \"debug 178\", \"statusType\": \"auto\", \"statusVal\": \"\", \"tosidebar\": true, \"tostatus\": false, \"type\": \"debug\", \"wires\": [], \"x\": 1670, \"y\": 3540, \"z\": \"02bb203de5d55ce5\"}, {\"active\": true, \"complete\": \"false\", \"console\": false, \"id\": \"08a896d7109ffe93\", \"name\": \"debug 179\", \"statusType\": \"auto\", \"statusVal\": \"\", \"tosidebar\": true, \"tostatus\": false, \"type\": \"debug\", \"wires\": [], \"x\": 1498, \"y\": 3499, \"z\": \"02bb203de5d55ce5\"}, {\"active\": true, \"complete\": \"false\", \"console\": false, \"id\": \"7822385efcf41610\", \"name\": \"debug 180\", \"statusType\": \"auto\", \"statusVal\": \"\", \"tosidebar\": true, \"tostatus\": false, \"type\": \"debug\", \"wires\": [], \"x\": 1530, \"y\": 3320, \"z\": \"02bb203de5d55ce5\"}, {\"env\": [{\"name\": \"LISTMONK_API_USER\", \"type\": \"str\", \"value\": \"user\"}, {\"name\": \"LISTMONK_API_KEY\", \"type\": \"env\", \"value\": \"xxxxxxxxxxxxxxxxx\"}, {\"name\": \"COMMAND\", \"type\": \"str\", \"value\": \"test_campaign\"}, {\"name\": \"TEST_EMAILS\", \"type\": \"json\", \"value\": \"[\\\"[email protected]\\\"]\"}], \"id\": \"f175cf628d7ef044\", \"name\": \"Test\", \"type\": \"subflow:539a548b549c19dd\", \"wires\": [[\"bfb80a88ec3011ed\"]], \"x\": 1490, \"y\": 3440, \"z\": \"02bb203de5d55ce5\"}, {\"env\": [{\"name\": \"LISTMONK_API_USER\", \"type\": \"str\", \"value\": \"user\"}, {\"name\": \"LISTMONK_API_KEY\", \"type\": \"env\", \"value\": \"xxxxxxxxxxxxxxxxx\"}, {\"name\": \"COMMAND\", \"type\": \"str\", \"value\": \"create_campaign\"}], \"id\": \"c4cf33d1546ce804\", \"name\": \"Create\", \"type\": \"subflow:539a548b549c19dd\", \"wires\": [[\"08a896d7109ffe93\", \"5a49ca3ca3c5f40d\"]], \"x\": 1330, \"y\": 3540, \"z\": \"02bb203de5d55ce5\"}, {\"env\": [{\"name\": \"LISTMONK_API_USER\", \"type\": \"str\", \"value\": \"user\"}, {\"name\": \"LISTMONK_API_KEY\", \"type\": \"env\", \"value\": \"xxxxxxxxxxxxxxxxx\"}, {\"name\": \"COMMAND\", \"type\": \"str\", \"value\": \"schedule_campaign\"}], \"id\": \"9757ed4f630165fa\", \"name\": \"Schedule\", \"type\": \"subflow:539a548b549c19dd\", \"wires\": [[\"18dbb5da307169cc\"]], \"x\": 1500, \"y\": 3380, \"z\": \"02bb203de5d55ce5\"}, {\"env\": [{\"name\": \"LISTMONK_API_USER\", \"type\": \"str\", \"value\": \"user\"}, {\"name\": \"LISTMONK_API_KEY\", \"type\": \"env\", \"value\": \"xxxxxxxxxxxxxxxxx\"}, {\"name\": \"COMMAND\", \"type\": \"str\", \"value\": \"start_campaign\"}], \"id\": \"5a49ca3ca3c5f40d\", \"name\": \"Start\", \"type\": \"subflow:539a548b549c19dd\", \"wires\": [[\"46faf6fb1e31620c\"]], \"x\": 1490, \"y\": 3540, \"z\": \"02bb203de5d55ce5\"}]\r\n```\r\n\r\n## Key changes and improvements:\r\n\r\n- **Campaign Management Section:** Added a dedicated section for campaign-related commands, improving organization.\r\n- **`TEST_EMAILS` Configuration:** Clearly documented the `TEST_EMAILS` variable, including its type (JSON array) and purpose. This is crucial for the `test_campaign` command.\r\n- **Detailed Command Documentation:** Expanded the documentation for each command, providing:\r\n - Clearer descriptions of input `msg.payload` requirements.\r\n - Specific examples for each command, including JSON payloads where appropriate.\r\n - Links to the relevant ListMonk API documentation where available.\r\n - Notes on specific behaviors (like the subject modification for test campaigns).\r\n- **`create_campaign` Details:** Provided a more detailed example and explanation of the common fields used in the `create_campaign` payload.\r\n- **`schedule_campaign`, `test_campaign`, `start_campaign` Details:** Added payload description and example.\r\n- **Error Handling:** Improved description of the \"unknown command\" error handling. Also added error handling for missing campaign ID.\r\n- **Clarity and Consistency:** Improved the overall clarity and consistency of language and formatting.\r\n- **Removed duplicated example json**: Removed example code from the doc.\r\n\r\n", "meta": {}, "name": "ListMonk", "out": [{"wires": [{"id": "935b77753e0823cb", "port": 1}, {"id": "19369549187b07c3", "port": 0}], "x": 1970, "y": 440}], "outputLabels": ["output"], "status": {"wires": [{"id": "46f4b59c2fbfcda0", "port": 0}, {"id": "1826a936f9df6011", "port": 0}, {"id": "39ed109e105c7b62", "port": 0}], "x": 1760, "y": 200}, "type": "subflow"}, {"finalize": "", "func": "const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst command = \"subscribers\";\n\nmsg.url = `${base_url}/${command}?per_page=all`;\n\nconst method = \"GET\";\n\nmsg.method = method;\n\nreturn msg;", "id": "d6802efb63a3ff91", "initialize": "", "libs": [], "name": "Get subscribers", "noerr": 0, "outputs": 1, "timeout": 0, "type": "function", "wires": [["18aa558621b3fc09"]], "x": 840, "y": 320, "z": "539a548b549c19dd"}, {"finalize": "", "func": "const user = env.get (\"LISTMONK_API_USER\");\nconst token = env.get (\"LISTMONK_API_KEY\");\n\nif (!token || !user) {\n node.error(\"No credentials defined for ListMonk service\");\n return;\n}\n\nmsg.headers = {\n Authorization: `token ${user}:${token}`\n}\n\nreturn msg;", "id": "18aa558621b3fc09", "initialize": "", "libs": [], "name": "Auth", "noerr": 0, "outputs": 1, "timeout": 0, "type": "function", "wires": [["e9cd8439e9e33482"]], "x": 1290, "y": 440, "z": "539a548b549c19dd"}, {"authType": "", "headers": [], "id": "e9cd8439e9e33482", "insecureHTTPParser": false, "method": "use", "name": "", "paytoqs": "ignore", "persist": false, "proxy": "", "ret": "obj", "senderr": false, "tls": "19e98c3.60b8c74", "type": "http request", "url": "", "wires": [["935b77753e0823cb", "46f4b59c2fbfcda0"]], "x": 1470, "y": 440, "z": "539a548b549c19dd"}, {"checkall": "true", "id": "935b77753e0823cb", "name": "", "outputLabels": ["get_subscriptions_by_userid", "other"], "outputs": 2, "property": "topic", "propertyType": "msg", "repair": false, "rules": [{"t": "eq", "v": "get_subscriptions_by_userid", "vt": "str"}, {"t": "else"}], "type": "switch", "wires": [["19369549187b07c3"], []], "x": 1630, "y": 440, "z": "539a548b549c19dd"}, {"finalize": "", "func": "if (msg.payload.data.lists) {\n msg.payload = msg.payload.data.lists;\n}\nreturn msg;", "id": "19369549187b07c3", "initialize": "", "libs": [], "name": "Get subscription list", "noerr": 0, "outputs": 1, "timeout": 0, "type": "function", "wires": [[]], "x": 1810, "y": 380, "z": "539a548b549c19dd"}, {"checkall": "true", "id": "3307f051db96af20", "name": "command", "outputLabels": ["get_subscriber", "get_subscriber_by_id", "get_subscriptions_by_userid", "add_subscription", "remove_subscription", "unsubscribe", "add_subscriber", "get_subscriber_by_attrib", "delete_subscriber_by_attrib", "create_campaign", "schedule_campaign", "test_campaign", "start_campaign", "transactional_send", "unknown"], "outputs": 15, "property": "topic", "propertyType": "msg", "repair": false, "rules": [{"t": "eq", "v": "get_subscribers", "vt": "str"}, {"t": "eq", "v": "get_subscriber_by_id", "vt": "str"}, {"t": "eq", "v": "get_subscriptions_by_userid", "vt": "str"}, {"t": "eq", "v": "add_subscription", "vt": "str"}, {"t": "eq", "v": "remove_subscription", "vt": "str"}, {"t": "eq", "v": "unsubscribe", "vt": "str"}, {"t": "eq", "v": "add_subscriber", "vt": "str"}, {"t": "eq", "v": "get_subscriber_by_attrib", "vt": "str"}, {"t": "eq", "v": "delete_subscriber_by_attrib", "vt": "str"}, {"t": "eq", "v": "create_campaign", "vt": "str"}, {"t": "eq", "v": "schedule_campaign", "vt": "str"}, {"t": "eq", "v": "test_campaign", "vt": "str"}, {"t": "eq", "v": "start_campaign", "vt": "str"}, {"t": "eq", "v": "transactional_send", "vt": "str"}, {"t": "else"}], "type": "switch", "wires": [["d6802efb63a3ff91"], ["4b82f73b7d4fbcec"], ["ae72fb6d3fe1d894"], ["b5cfcbda104dd714"], ["719533cf5ca15e07"], ["ad71386962404079"], ["268ee04c7434de4d"], ["e86eb12c15f1889f"], ["f687003d0d4665fa"], ["b5bb8d6c07a22b04"], ["2ab277fe9e949ee8"], ["7a4239c193223cb4"], ["e3ddea1da6bac262"], ["20593fa4fe750c81"], ["5b646c17ebe144a2", "39ed109e105c7b62"]], "x": 540, "y": 520, "z": "539a548b549c19dd"}, {"crontab": "", "id": "ebf3df5fd3b147c0", "name": "", "once": true, "onceDelay": 0.1, "payload": "COMMAND", "payloadType": "env", "props": [{"p": "payload"}], "repeat": "", "topic": "", "type": "inject", "wires": [["1826a936f9df6011"]], "x": 1390, "y": 160, "z": "539a548b549c19dd"}, {"finalize": "", "func": "msg.payload= {\n text: msg.topic,\n shape: \"dot\"\n}\n\nif (msg.statusCode == 200) {\n msg.payload.fill = \"green\";\n} else {\n msg.payload.fill = \"red\";\n}\n\nreturn msg;\n", "id": "46f4b59c2fbfcda0", "initialize": "", "libs": [], "name": "Request result", "noerr": 0, "outputLabels": ["msg"], "outputs": 1, "timeout": 0, "type": "function", "wires": [[]], "x": 1560, "y": 260, "z": "539a548b549c19dd"}, {"finalize": "", "func": "const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst command = \"subscribers\";\n\nmsg.url = `${base_url}/${command}/${msg.payload}`;\n\nconst method = \"GET\";\n\nmsg.method = method;\n\nreturn msg;", "id": "4b82f73b7d4fbcec", "initialize": "", "libs": [], "name": "Get subscriber by id", "noerr": 0, "outputs": 1, "timeout": 0, "type": "function", "wires": [["18aa558621b3fc09"]], "x": 860, "y": 360, "z": "539a548b549c19dd"}, {"finalize": "", "func": "const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst command = \"subscribers\";\n\nmsg.url = `${base_url}/${command}/${msg.payload}`;\n\nconst method = \"GET\";\n\nmsg.method = method;\n\nreturn msg;", "id": "ae72fb6d3fe1d894", "initialize": "", "libs": [], "name": "Get subscriptions by user id", "noerr": 0, "outputs": 1, "timeout": 0, "type": "function", "wires": [["18aa558621b3fc09"]], "x": 880, "y": 400, "z": "539a548b549c19dd"}, {"finalize": "", "func": "const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst command = \"subscribers/lists\";\n\nmsg.url = `${base_url}/${command}`;\n\nconst method = \"PUT\";\n\nmsg.method = method;\n\nmsg.payload = {\n ids: [msg.payload.user],\n action: \"add\",\n target_list_ids: [msg.payload.list],\n status: \"confirmed\"\n}\n\nreturn msg;", "id": "b5cfcbda104dd714", "initialize": "", "libs": [], "name": "Add subscription to user", "noerr": 0, "outputs": 1, "timeout": 0, "type": "function", "wires": [["18aa558621b3fc09"]], "x": 870, "y": 480, "z": "539a548b549c19dd"}, {"finalize": "", "func": "const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst command = \"subscribers/lists\";\n\nmsg.url = `${base_url}/${command}`;\n\nconst method = \"PUT\";\n\nmsg.method = method;\n\nmsg.payload = {\n ids: [msg.payload.user],\n action: \"remove\",\n target_list_ids: [msg.payload.list],\n status: \"confirmed\"\n}\n\nreturn msg;", "id": "719533cf5ca15e07", "initialize": "", "libs": [], "name": "Remove subscription from user", "noerr": 0, "outputs": 1, "timeout": 0, "type": "function", "wires": [["18aa558621b3fc09"]], "x": 890, "y": 520, "z": "539a548b549c19dd"}, {"finalize": "", "func": "const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst command = \"subscribers/lists\";\n\nmsg.url = `${base_url}/${command}`;\n\nconst method = \"PUT\";\n\nmsg.method = method;\n\nmsg.payload = {\n ids: [msg.payload.user],\n action: \"unsubscribe\",\n target_list_ids: [msg.payload.list],\n status: \"confirmed\"\n}\n\nreturn msg;", "id": "ad71386962404079", "initialize": "", "libs": [], "name": "User unsubscribe subscription", "noerr": 0, "outputs": 1, "timeout": 0, "type": "function", "wires": [["18aa558621b3fc09"]], "x": 890, "y": 560, "z": "539a548b549c19dd"}, {"finalize": "", "func": "const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst command = \"subscribers\";\n\nmsg.url = `${base_url}/${command}`;\n\nconst method = \"POST\";\n\nmsg.method = method;\n\nreturn msg;", "id": "268ee04c7434de4d", "initialize": "", "libs": [], "name": "Add subscriber", "noerr": 0, "outputs": 1, "timeout": 0, "type": "function", "wires": [["18aa558621b3fc09"]], "x": 840, "y": 600, "z": "539a548b549c19dd"}, {"finalize": "", "func": "const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst key = msg.payload[0];\nconst value = msg.payload[1];\n\nconst query = `subscribers.attribs->>'${key}'='${value}'`;\n\nmsg.payload = {\n query: query\n}\n\nconst command = \"subscribers/query/delete\";\n\nmsg.url = `${base_url}/${command}`;\n\nconst method = \"POST\";\n\nmsg.method = method;\n\nreturn msg;", "id": "f687003d0d4665fa", "initialize": "", "libs": [], "name": "Delete subscriber by tinygs user", "noerr": 0, "outputs": 1, "timeout": 0, "type": "function", "wires": [["18aa558621b3fc09"]], "x": 890, "y": 680, "z": "539a548b549c19dd"}, {"checkall": "true", "id": "e4d6a43404fcd1aa", "name": "", "outputLabels": ["from topic", "command"], "outputs": 2, "property": "COMMAND", "propertyType": "env", "repair": false, "rules": [{"t": "eq", "v": "topic", "vt": "str"}, {"t": "else"}], "type": "switch", "wires": [["3307f051db96af20"], ["870a592b90cdadf0"]], "x": 230, "y": 400, "z": "539a548b549c19dd"}, {"finalize": "", "func": "msg.topic = env.get(\"COMMAND\");\nreturn msg;", "id": "870a592b90cdadf0", "initialize": "", "libs": [], "name": "from command", "noerr": 0, "outputs": 1, "timeout": 0, "type": "function", "wires": [["3307f051db96af20"]], "x": 300, "y": 520, "z": "539a548b549c19dd"}, {"d": true, "finalize": "", "func": "msg.payload = {\n result: `unknown command: ${msg.topic}`,\n original_data: msg.payload\n};\nreturn msg;", "id": "5b646c17ebe144a2", "initialize": "", "libs": [], "name": "unknown command", "noerr": 0, "outputs": 1, "timeout": 0, "type": "function", "wires": [[]], "x": 270, "y": 640, "z": "539a548b549c19dd"}, {"finalize": "", "func": "msg.payload= {\n text: `unknown: ${msg.topic}`,\n shape: \"dot\",\n fill: \"red\"\n}\n\nreturn msg;\n", "id": "39ed109e105c7b62", "initialize": "", "libs": [], "name": "Unknown command", "noerr": 0, "outputLabels": ["msg"], "outputs": 1, "timeout": 0, "type": "function", "wires": [[]], "x": 660, "y": 280, "z": "539a548b549c19dd"}, {"finalize": "", "func": "msg.payload= {\n text: msg.payload,\n shape: \"dot\",\n fill: \"grey\"\n}\n\nreturn msg;\n", "id": "1826a936f9df6011", "initialize": "", "libs": [], "name": "Command", "noerr": 0, "outputLabels": ["msg"], "outputs": 1, "timeout": 0, "type": "function", "wires": [[]], "x": 1570, "y": 160, "z": "539a548b549c19dd"}, {"finalize": "", "func": "const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst key = msg.payload[0];\nconst value = msg.payload[1];\n\nconst query = `subscribers.attribs->>'${key}'='${value}'`;\n\nconst command = \"subscribers\";\n\nmsg.url = `${base_url}/${command}?query=${query}`;\n\nconst method = \"GET\";\n\nmsg.method = method;\n\nreturn msg;", "id": "e86eb12c15f1889f", "initialize": "", "libs": [], "name": "Get subscriber by attrib", "noerr": 0, "outputs": 1, "timeout": 0, "type": "function", "wires": [["18aa558621b3fc09"]], "x": 870, "y": 640, "z": "539a548b549c19dd"}, {"id": "9a05d7fbd929753a", "info": "", "name": "Output2", "type": "comment", "wires": [], "x": 470, "y": 640, "z": "539a548b549c19dd"}, {"finalize": "", "func": "const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst command = \"campaigns\";\n\nmsg.url = `${base_url}/${command}`;\n\nconst method = \"POST\";\n\nmsg.method = method;\n\nreturn msg;", "id": "b5bb8d6c07a22b04", "initialize": "", "libs": [], "name": "Create Campaign", "noerr": 0, "outputs": 1, "timeout": 0, "type": "function", "wires": [["18aa558621b3fc09"]], "x": 850, "y": 720, "z": "539a548b549c19dd"}, {"finalize": "", "func": "const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst id = msg.payload.data.id ?? null;\nif (!id) {\n node.error(\"No campaign id provided\", msg);\n return;\n}\n\nconst command = `campaigns/${id}/status`;\n\nmsg.url = `${base_url}/${command}`;\n\nconst method = \"PUT\";\n\nmsg.method = method;\n\nmsg.payload = {\n status: \"scheduled\"\n};\n\nreturn msg;", "id": "2ab277fe9e949ee8", "initialize": "", "libs": [], "name": "Schedule Campaign", "noerr": 0, "outputs": 1, "timeout": 0, "type": "function", "wires": [["18aa558621b3fc09"]], "x": 860, "y": 760, "z": "539a548b549c19dd"}, {"finalize": "", "func": "const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst id = msg.payload.data.id ?? null;\nif (!id) {\n node.error(\"No campaign id provided\", msg);\n return;\n}\n\nconst command = `campaigns/${id}/test`;\n\nmsg.url = `${base_url}/${command}`;\n\nconst method = \"POST\";\n\nmsg.method = method;\n\nlet emails = env.get(\"TEST_EMAILS\");\n\nif (typeof emails !== 'string') {\n emails = String(emails || ''); // Convierte a cadena o inicializa vacía\n}\n\n// Convertir la cadena en un array\nconst emailArray = emails\n .split(',') // Divide por comas\n .map(email => email.trim()) // Elimina espacios en blanco\n .filter(email => email !== ''); // Elimina entradas vacías\n\nmsg.payload = {\n subscribers: emailArray,\n name: msg.payload.data.name,\n subject: `${msg.payload.data.subject} (test)`,\n lists: [msg.payload.data.lists[0].id],\n from_email: msg.payload.data.from_email,\n type: msg.payload.data.type,\n content_type: msg.payload.data.content_type,\n body: msg.payload.data.body,\n template_id: msg.payload.data.template_id,\n messenger: msg.payload.data.messenger,\n tags: msg.payload.data.tags\n};\n\nreturn msg;\n", "id": "7a4239c193223cb4", "initialize": "", "libs": [], "name": "Test Campaign", "noerr": 0, "outputs": 1, "timeout": 0, "type": "function", "wires": [["18aa558621b3fc09"]], "x": 840, "y": 800, "z": "539a548b549c19dd"}, {"finalize": "", "func": "const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst id = msg.payload.data.id ?? null;\nif (!id) {\n node.error(\"No campaign id provided\", msg);\n return;\n}\n\nconst command = `campaigns/${id}/status`;\n\nmsg.url = `${base_url}/${command}`;\n\nconst method = \"PUT\";\n\nmsg.method = method;\n\nmsg.payload = {\n status: \"running\"\n};\n\nreturn msg;", "id": "e3ddea1da6bac262", "initialize": "", "libs": [], "name": "Start Campaign", "noerr": 0, "outputs": 1, "timeout": 0, "type": "function", "wires": [["18aa558621b3fc09"]], "x": 840, "y": 840, "z": "539a548b549c19dd"}, {"finalize": "", "func": "const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst id = msg.payload.data ?? null;\nif (!id) {\n node.error(\"No content provided\", msg);\n return;\n}\n\nconst command = `tx`;\n\nmsg.url = `${base_url}/${command}`;\n\nconst method = \"POST\";\n\nmsg.method = method;\n\nreturn msg;", "id": "20593fa4fe750c81", "initialize": "", "libs": [], "name": "transactional_send", "noerr": 0, "outputs": 1, "timeout": 0, "type": "function", "wires": [["18aa558621b3fc09"]], "x": 850, "y": 880, "z": "539a548b549c19dd"}, {"ca": "", "caname": "", "cert": "", "certname": "", "id": "19e98c3.60b8c74", "key": "", "keyname": "", "name": "default", "servername": "", "type": "tls-config", "verifyservercert": true}, {"env": [{"name": "LISTMONK_API_USER", "type": "str", "value": "my_api_user"}, {"name": "LISTMONK_API_KEY", "type": "str", "value": "xxxxxxxxxxxxxxxxx"}], "id": "a695d7033f713e4a", "name": "", "type": "subflow:539a548b549c19dd", "wires": [["bc6df03166084a21"]], "x": 1560, "y": 3120, "z": "02bb203de5d55ce5"}, {"crontab": "", "id": "75822a732d8d77c2", "name": "", "once": false, "onceDelay": 0.1, "payload": "", "payloadType": "str", "props": [{"p": "payload"}, {"p": "topic", "vt": "str"}], "repeat": "", "topic": "get_subscribers", "type": "inject", "wires": [["a695d7033f713e4a"]], "x": 1200, "y": 2960, "z": "02bb203de5d55ce5"}, {"crontab": "", "id": "2d26b96d9a2cbc8d", "name": "add_subscription", "once": false, "onceDelay": 0.1, "payload": "{\"user\":3,\"list\":4}", "payloadType": "json", "props": [{"p": "payload"}, {"p": "topic", "vt": "str"}], "repeat": "", "topic": "add_subscription", "type": "inject", "wires": [["a695d7033f713e4a"]], "x": 1140, "y": 3120, "z": "02bb203de5d55ce5"}, {"crontab": "", "id": "0d034144e9fad648", "name": "remove_subscription", "once": false, "onceDelay": 0.1, "payload": "{\"user\":3,\"list\":4}", "payloadType": "json", "props": [{"p": "payload"}, {"p": "topic", "vt": "str"}], "repeat": "", "topic": "remove_subscription", "type": "inject", "wires": [["a695d7033f713e4a"]], "x": 1130, "y": 3160, "z": "02bb203de5d55ce5"}, {"crontab": "", "id": "9c1707deb912fcf8", "name": "get_subscriber_by_attrib", "once": false, "onceDelay": 0.1, "payload": "[\"internal_user\",1234567890]", "payloadType": "json", "props": [{"p": "payload"}, {"p": "topic", "vt": "str"}], "repeat": "", "topic": "get_subscriber_by_attrib", "type": "inject", "wires": [["a695d7033f713e4a"]], "x": 1170, "y": 3280, "z": "02bb203de5d55ce5"}, {"crontab": "", "id": "3d0cec56c5b275c8", "name": "delete_subscriber_by_attrib", "once": false, "onceDelay": 0.1, "payload": "[\"internal_user\",1234567890]", "payloadType": "json", "props": [{"p": "payload"}, {"p": "topic", "vt": "str"}], "repeat": "", "topic": "delete_subscriber_by_attrib", "type": "inject", "wires": [["a695d7033f713e4a"]], "x": 1160, "y": 3320, "z": "02bb203de5d55ce5"}, {"crontab": "", "id": "47f34392c4c4b923", "name": "add_subscriber", "once": false, "onceDelay": 0.1, "payload": "{\"email\":\"[email protected]\",\"name\":\"test4\",\"status\":\"enabled\",\"lists\":[2],\"attribs\":{\"internal_user\":\"1234567890\"},\"preconfirm_subscriptions\":true}", "payloadType": "json", "props": [{"p": "payload"}, {"p": "topic", "vt": "str"}], "repeat": "", "topic": "add_subscriber", "type": "inject", "wires": [["a695d7033f713e4a"]], "x": 1200, "y": 3240, "z": "02bb203de5d55ce5"}, {"active": true, "complete": "false", "console": false, "id": "bc6df03166084a21", "name": "debug 171", "statusType": "auto", "statusVal": "", "tosidebar": true, "tostatus": false, "type": "debug", "wires": [], "x": 1750, "y": 3120, "z": "02bb203de5d55ce5"}, {"crontab": "", "id": "420850a07614dd09", "name": "create scheduled campaign", "once": false, "onceDelay": 0.1, "payload": "{\"name\":\"Daily Newsletter\",\"subject\":\"My news letter\",\"lists\":[1],\"body\":\"Test\",\"from_email\":\"Sender \",\"type\":\"regular\",\"content_type\":\"html\",\"template_id\":1,\"tags\":[\"maillist\"],\"send_at\":\"2025-03-25T10:00:00Z\"}", "payloadType": "json", "props": [{"p": "payload"}, {"p": "topic", "vt": "str"}], "repeat": "", "topic": "create_campaign", "type": "inject", "wires": [["f6ab457865b2e961"]], "x": 1200, "y": 3420, "z": "02bb203de5d55ce5"}, {"env": [{"name": "LISTMONK_API_USER", "type": "str", "value": "user"}, {"name": "LISTMONK_API_KEY", "type": "env", "value": "xxxxxxxxxxxxxxxxx"}, {"name": "COMMAND", "type": "str", "value": "create_campaign"}], "id": "f6ab457865b2e961", "name": "Create", "type": "subflow:539a548b549c19dd", "wires": [["7822385efcf41610", "9757ed4f630165fa", "f175cf628d7ef044"]], "x": 1390, "y": 3420, "z": "02bb203de5d55ce5"}, {"active": true, "complete": "false", "console": false, "id": "18dbb5da307169cc", "name": "debug 176", "statusType": "auto", "statusVal": "", "tosidebar": true, "tostatus": false, "type": "debug", "wires": [], "x": 1747, "y": 3399, "z": "02bb203de5d55ce5"}, {"active": true, "complete": "false", "console": false, "id": "bfb80a88ec3011ed", "name": "debug 177", "statusType": "auto", "statusVal": "", "tosidebar": true, "tostatus": false, "type": "debug", "wires": [], "x": 1750, "y": 3460, "z": "02bb203de5d55ce5"}, {"crontab": "", "id": "5fc9aea7b927d50a", "name": "create and start campaign", "once": false, "onceDelay": 0.1, "payload": "{\"name\":\"Daily Newsletter\",\"subject\":\"My news letter\",\"lists\":[1],\"body\":\"Test\",\"from_email\":\"Sender \",\"type\":\"regular\",\"content_type\":\"html\",\"template_id\":1,\"tags\":[\"maillist\"]}", "payloadType": "json", "props": [{"p": "payload"}, {"p": "topic", "vt": "str"}], "repeat": "", "topic": "start_campaign", "type": "inject", "wires": [["c4cf33d1546ce804"]], "x": 1170, "y": 3560, "z": "02bb203de5d55ce5"}, {"active": true, "complete": "false", "console": false, "id": "46faf6fb1e31620c", "name": "debug 178", "statusType": "auto", "statusVal": "", "tosidebar": true, "tostatus": false, "type": "debug", "wires": [], "x": 1730, "y": 3560, "z": "02bb203de5d55ce5"}, {"active": true, "complete": "false", "console": false, "id": "08a896d7109ffe93", "name": "debug 179", "statusType": "auto", "statusVal": "", "tosidebar": true, "tostatus": false, "type": "debug", "wires": [], "x": 1558, "y": 3519, "z": "02bb203de5d55ce5"}, {"active": true, "complete": "false", "console": false, "id": "7822385efcf41610", "name": "debug 180", "statusType": "auto", "statusVal": "", "tosidebar": true, "tostatus": false, "type": "debug", "wires": [], "x": 1590, "y": 3340, "z": "02bb203de5d55ce5"}, {"env": [{"name": "LISTMONK_API_USER", "type": "str", "value": "user"}, {"name": "LISTMONK_API_KEY", "type": "env", "value": "xxxxxxxxxxxxxxxxx"}, {"name": "COMMAND", "type": "str", "value": "test_campaign"}, {"name": "TEST_EMAILS", "type": "json", "value": "[\"[email protected]\"]"}], "id": "f175cf628d7ef044", "name": "Test", "type": "subflow:539a548b549c19dd", "wires": [["bfb80a88ec3011ed"]], "x": 1550, "y": 3460, "z": "02bb203de5d55ce5"}, {"env": [{"name": "LISTMONK_API_USER", "type": "str", "value": "user"}, {"name": "LISTMONK_API_KEY", "type": "env", "value": "xxxxxxxxxxxxxxxxx"}, {"name": "COMMAND", "type": "str", "value": "create_campaign"}], "id": "c4cf33d1546ce804", "name": "Create", "type": "subflow:539a548b549c19dd", "wires": [["08a896d7109ffe93", "5a49ca3ca3c5f40d"]], "x": 1390, "y": 3560, "z": "02bb203de5d55ce5"}, {"env": [{"name": "LISTMONK_API_USER", "type": "str", "value": "user"}, {"name": "LISTMONK_API_KEY", "type": "env", "value": "xxxxxxxxxxxxxxxxx"}, {"name": "COMMAND", "type": "str", "value": "schedule_campaign"}], "id": "9757ed4f630165fa", "name": "Schedule", "type": "subflow:539a548b549c19dd", "wires": [["18dbb5da307169cc"]], "x": 1560, "y": 3400, "z": "02bb203de5d55ce5"}, {"env": [{"name": "LISTMONK_API_USER", "type": "str", "value": "user"}, {"name": "LISTMONK_API_KEY", "type": "env", "value": "xxxxxxxxxxxxxxxxx"}, {"name": "COMMAND", "type": "str", "value": "start_campaign"}], "id": "5a49ca3ca3c5f40d", "name": "Start", "type": "subflow:539a548b549c19dd", "wires": [["46faf6fb1e31620c"]], "x": 1550, "y": 3560, "z": "02bb203de5d55ce5"}]
[{"id":"539a548b549c19dd","type":"subflow","name":"ListMonk","info":"# ListMonk Subflow\r\n\r\nThis Node-RED subflow provides an interface to interact with a [ListMonk](https://listmonk.app/) mailing list server. It allows you to perform various operations, such as retrieving subscribers, managing subscriptions, adding/deleting subscribers, and managing campaigns. The subflow uses the ListMonk REST API.\r\n\r\n## Table of Contents\r\n\r\n- [Configuration](#configuration)\r\n- [Inputs](#inputs)\r\n- [Outputs](#outputs)\r\n- [Commands](#commands)\r\n - [Subscriber Management](#subscriber-management)\r\n - [get_subscribers](#get_subscribers)\r\n - [get_subscriber_by_id](#get_subscriber_by_id)\r\n - [get_subscriptions_by_userid](#get_subscriptions_by_userid)\r\n - [add_subscription](#add_subscription)\r\n - [remove_subscription](#remove_subscription)\r\n - [unsubscribe](#unsubscribe)\r\n - [add_subscriber](#add_subscriber)\r\n - [get_subscriber_by_attrib](#get_subscriber_by_attrib)\r\n - [delete_subscriber_by_attrib](#delete_subscriber_by_attrib)\r\n - [Campaign Management](#campaign-management)\r\n - [create_campaign](#create_campaign)\r\n - [schedule_campaign](#schedule_campaign)\r\n - [test_campaign](#test_campaign)\r\n - [start_campaign](#start_campaign)\r\n- [Error Handling](#error-handling)\r\n- [Example Usage](#example-usage)\r\n\r\n## Configuration\r\n\r\nThe subflow requires the following configuration parameters, which can be set as environment variables within the subflow's configuration:\r\n\r\n| Parameter | Type | Description |\r\n| ------------------- | ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\r\n| `LISTMONK_BASE_URL` | str | The base URL of your ListMonk instance (e.g., `https://your-listmonk-instance.com/api`). **Required.** |\r\n| `LISTMONK_API_USER` | str | The username for your ListMonk API user. **Required.** |\r\n| `LISTMONK_API_KEY` | str | The API key for your ListMonk API user. **Required.** |\r\n| `COMMAND` | str | The default command to execute. This can be overridden by the `msg.topic` of the incoming message. See [Commands](#commands) for available options. Defaults to getting the command from `msg.topic`. |\r\n| `TEST_EMAILS` | json | An array of email addresses to use for testing campaigns. **Required for the `test_campaign` command.** This should be a JSON array of strings (e.g., `[\"[email protected]\", \"[email protected]\"]`). Defaults to an empty array (`[]`). |\r\n\r\nThese parameters are used to construct the API requests to your ListMonk server. Without these, the subflow will not function correctly. You can use either string values directly in the subflow configuration or the names of global environment variables.\r\n\r\n## Inputs\r\n\r\nThe subflow accepts a single input, which is a standard Node-RED message object (`msg`). The key property is `msg.topic`, which determines the action to be performed. `msg.payload` is used to pass data to the specific command.\r\n\r\n- **`msg.topic`** (string): The command to execute. This *overrides* the `COMMAND` variable. See the [Commands](#commands) section for a list of valid commands. If `msg.topic` is not provided (or is empty), and the subflow's `COMMAND` variable is set to \"topic\", then no action is taken.\r\n- **`msg.payload`** (various): The data required by the specific command. The format of `msg.payload` varies depending on the command. See the documentation for each command below.\r\n\r\n## Outputs\r\n\r\nThe subflow has a single output. The output message (`msg`) will contain the following:\r\n\r\n- **`msg.payload`** (object or array): The response from the ListMonk API. The structure of this data depends on the command executed. See the ListMonk API documentation for details on the response formats. For `get_subscriptions_by_userid`, the subflow extracts the `lists` array from the response data and sets it as `msg.payload`.\r\n- **`msg.statusCode`** (number): The HTTP status code of the API request. A 200 status code generally indicates success.\r\n- **`msg.headers`** (object): The HTTP headers returned by the ListMonk API.\r\n- **`msg.topic`**: The command that was executed.\r\n\r\n## Commands\r\n\r\nThe following commands are supported by the subflow. These commands correspond to endpoints in the ListMonk API. The commands are grouped into Subscriber Management and Campaign Management.\r\n\r\n### Subscriber Management\r\n\r\n#### `get_subscribers`\r\n\r\nRetrieves all subscribers from ListMonk.\r\n\r\n- **Input (`msg.payload`):** Not used.\r\n- **Output (`msg.payload`):** An array of subscriber objects. See the [ListMonk API documentation](https://listmonk.app/docs/apis/subscribers/#get-apisubscribers) for the structure of the subscriber object.\r\n- **Example:** Send a message with `msg.topic = \"get_subscribers\"`.\r\n\r\n#### `get_subscriber_by_id`\r\n\r\nRetrieves a single subscriber by their ID.\r\n\r\n- **Input (`msg.payload`):** (number) The ID of the subscriber to retrieve.\r\n- **Output (`msg.payload`):** A single subscriber object. See the [ListMonk API documentation](https://listmonk.app/docs/apis/subscribers/#get-apisubscriberssubscriber_id).\r\n- **Example:** Send a message with `msg.topic = \"get_subscriber_by_id\"` and `msg.payload = 123` (where 123 is the subscriber ID).\r\n\r\n#### `get_subscriptions_by_userid`\r\n\r\nRetrieves the subscriptions for a specific user ID. This uses the same Listmonk API endpoint as `get_subscriber_by_id`. The subflow extracts the relevant part of the result.\r\n\r\n- **Input (`msg.payload`):** (number) The ID of the subscriber.\r\n- **Output (`msg.payload`):** An array of list objects representing the subscriber's subscriptions. The subflow transforms the raw API response to only include the `lists` data.\r\n- **Example:** Send a message with `msg.topic = \"get_subscriptions_by_userid\"` and `msg.payload = 123`.\r\n\r\n#### `add_subscription`\r\n\r\nAdds a subscriber to a specific list.\r\n\r\n- **Input (`msg.payload`):** (object) An object with the following properties:\r\n - `user` (number): The ID of the subscriber.\r\n - `list` (number): The ID of the list to subscribe the user to.\r\n- **Output (`msg.payload`):** The API response from ListMonk. See the [ListMonk API documentation](https://listmonk.app/docs/apis/subscribers/#put-apisubscriberslists).\r\n- **Example:** Send a message with `msg.topic = \"add_subscription\"` and `msg.payload = { user: 123, list: 456 }`.\r\n\r\n#### `remove_subscription`\r\n\r\nRemoves a subscriber from a specific list.\r\n\r\n- **Input (`msg.payload`):** (object) An object with the following properties:\r\n - `user` (number): The ID of the subscriber.\r\n - `list` (number): The ID of the list to unsubscribe the user from.\r\n- **Output (`msg.payload`):** The API response from ListMonk.\r\n- **Example:** Send a message with `msg.topic = \"remove_subscription\"` and `msg.payload = { user: 123, list: 456 }`.\r\n\r\n#### `unsubscribe`\r\n\r\nUnsubscribes a user from a specific list. This is functionally identical to `remove_subscription` in this subflow.\r\n\r\n- **Input (`msg.payload`):** (object) An object with the following properties:\r\n - `user` (number): The ID of the subscriber.\r\n - `list` (number): The ID of the list to unsubscribe the user from.\r\n- **Output (`msg.payload`):** The API response from ListMonk.\r\n- **Example:** Send a message with `msg.topic = \"unsubscribe\"` and `msg.payload = { user: 123, list: 456 }`.\r\n\r\n#### `add_subscriber`\r\n\r\nAdds a new subscriber to ListMonk.\r\n\r\n- **Input (`msg.payload`):** (object) A subscriber object. See the [ListMonk API documentation](https://listmonk.app/docs/apis/subscribers/#post-apisubscribers) for the required fields. At a minimum, you'll likely need `email`, `name`, and `lists` (an array of list IDs). The subflow doesn't perform any validation of this object.\r\n- **Output (`msg.payload`):** The API response from ListMonk, which will include the newly created subscriber object.\r\n- **Example:** Send a message with:\r\n ```json\r\n {\r\n \"topic\": \"add_subscriber\",\r\n \"payload\": {\r\n \"email\": \"[email protected]\",\r\n \"name\": \"Test User\",\r\n \"lists\": [1],\r\n \"attribs\": {\r\n \"city\": \"Anytown\"\r\n },\r\n \"preconfirm_subscriptions\": true\r\n }\r\n }\r\n ```\r\n\r\n#### `get_subscriber_by_attrib`\r\n\r\nRetrieves subscribers based on a custom attribute.\r\n\r\n- **Input (`msg.payload`):** (array) A two-element array containing:\r\n - `[0]` (string): The name of the custom attribute (e.g., \"internalUserId\"). This attribute must be defined in your ListMonk subscriber attributes.\r\n - `[1]` (string or number): The value of the attribute to search for.\r\n- **Output (`msg.payload`):** An array of subscribers that match the query.\r\n- **Example:** Send a message with `msg.topic = \"get_subscriber_by_attrib\"` and `msg.payload = [\"internalUserId\", \"1234567890\"]`.\r\n\r\n#### `delete_subscriber_by_attrib`\r\n\r\nDeletes subscribers based on custom attributes.\r\n\r\n- **Input (`msg.payload`):** (array) A two-element array containing:\r\n - `[0]` (string): The name of the custom attribute (e.g., \"internalUserId\").\r\n - `[1]` (string or number): The value of the attribute.\r\n- **Output (`msg.payload`):** The API response from ListMonk.\r\n- **Example:** Send a message with `msg.topic = \"delete_subscriber_by_attrib\"` and `msg.payload = [\"internalUserId\", \"1234567890\"]`.\r\n\r\n### Campaign Management\r\n\r\n#### `create_campaign`\r\n\r\nCreates a new campaign in ListMonk.\r\n\r\n- **Input (`msg.payload`):** (object) A campaign object. See the [ListMonk API documentation](https://listmonk.app/docs/apis/campaigns/#post-apicampaigns) for the available fields. The following fields are commonly used:\r\n - `name` (string): The name of the campaign.\r\n - `subject` (string): The subject line of the campaign.\r\n - `lists` (array): An array of list IDs to send the campaign to.\r\n - `body` (string): The HTML body of the campaign.\r\n - `from_email` (string): The \"from\" email address.\r\n - `type` (string): The campaign type (`regular` or `optin`).\r\n - `content_type` (string): `html` or `plain`.\r\n - `template_id` (number, optional): The ID of a template to use.\r\n - `send_at` (string, optional): Date and time for schedule a campaign.\r\n - `tags` (array, optional): An array of tags.\r\n- **Output (`msg.payload`):** The API response from ListMonk, including the newly created campaign object.\r\n- **Example:**\r\n ```json\r\n {\r\n \"topic\": \"create_campaign\",\r\n \"payload\": {\r\n \"name\": \"My Campaign\",\r\n \"subject\": \"My Newsletter\",\r\n \"lists\": [1, 2],\r\n \"body\": \"<html><body><h1>Hello!</h1></body></html>\",\r\n \"from_email\": \"[email protected]\",\r\n \"type\": \"regular\",\r\n \"content_type\": \"html\",\r\n \"tags\": [\"newsletter\", \"promotional\"]\r\n }\r\n }\r\n ```\r\n\r\n#### `schedule_campaign`\r\n\r\nSchedules a campaign to be sent. This changes the campaign's status to \"scheduled\".\r\n\r\n- **Input (`msg.payload`):** (object) an object containing a `data` field. Inside must contain `id` field.\r\n - `id` (number): The ID of the campaign to schedule.\r\n- **Output (`msg.payload`):** The API response from ListMonk.\r\n- **Example:**\r\n ```json\r\n {\r\n \"topic\": \"schedule_campaign\",\r\n \"payload\": {\r\n \"data\": {\r\n \"id\": 75\r\n }\r\n }\r\n }\r\n ```\r\n\r\n#### `test_campaign`\r\n\r\nSends a test email for a campaign. **Requires the `TEST_EMAILS` variable to be set.**\r\n\r\n- **Input (`msg.payload`):** (object) An object with a `data` property, which is itself an object containing campaign data. It is normally the output of [Create campaign command](#create_campaign)\r\n\r\n- **Output (`msg.payload`):** The API response from ListMonk.\r\n\r\n- **Example:**\r\n```json\r\n{\r\n \"topic\": \"test_campaign\",\r\n \"payload\": {\r\n \"data\": {\r\n \"id\": 70\r\n }\r\n }\r\n}\r\n```\r\n\r\n- Notes:\r\n\t- The TEST_EMAILS variable must be a JSON array of email addresses. The test email will be sent to all addresses in this array.\r\n\t- The subject of the test email will have (test) appended to the campaign's original subject.\r\n\r\n#### `start_campaign`\r\n\r\nStarts a campaign. This changes the campaign's status to \"running\".\r\n\r\n- **Input (`msg.payload`):** (object) an object containing a data field. Inside must contain `id` field.\r\n\t- **id (number):** The ID of the campaign to schedule.\r\n\r\n- **Output (`msg.payload`):** The API response from ListMonk.\r\n\r\n- Example:\r\n\r\n```json\r\n{\r\n \"topic\": \"start_campaign\",\r\n \"payload\": {\r\n \"data\": {\r\n \"id\": 75\r\n }\r\n }\r\n}\r\n```\r\n\r\n## Error Handling\r\n\r\n- If the LISTMONK_BASE_URL, LISTMONK_API_USER, or LISTMONK_API_KEY variables are not set, the subflow will log an error to the Node-RED console using node.error(). The flow will not proceed.\r\n- The subflow uses an http request node. Any errors from this node (e.g., network errors, invalid responses) will be passed through to the output of the subflow. The msg.statusCode will reflect the HTTP error code.\r\n- If an unknown command is used and the switch node reaches the \"unknown command\" function node, the status of the subflow node will change to a red dot with the text \"unknown: command\". An error message will also be sent to the Node-RED debug pane.\r\n- If the id campaign is missing on schedule_campaign, test_campaign or start_campaign commands, the subflow will log an error to the Node-RED console using node.error().\r\n\r\n## Example Usage\r\n\r\nThe provided flow includes several inject nodes that demonstrate how to use the subflow with different commands. You can import this flow into Node-RED and modify the inject nodes to test the subflow with your ListMonk instance. Remember to set the LISTMONK_BASE_URL, LISTMONK_API_USER, LISTMONK_API_KEY, and TEST_EMAILS variables in the subflow configuration.\r\n\r\n```json\r\n[{\"env\": [{\"name\": \"LISTMONK_API_USER\", \"type\": \"str\", \"value\": \"my_api_user\"}, {\"name\": \"LISTMONK_API_KEY\", \"type\": \"str\", \"value\": \"xxxxxxxxxxxxxxxxx\"}], \"id\": \"a695d7033f713e4a\", \"name\": \"\", \"type\": \"subflow:539a548b549c19dd\", \"wires\": [[\"bc6df03166084a21\"]], \"x\": 1500, \"y\": 3100, \"z\": \"02bb203de5d55ce5\"}, {\"crontab\": \"\", \"id\": \"75822a732d8d77c2\", \"name\": \"\", \"once\": false, \"onceDelay\": 0.1, \"payload\": \"\", \"payloadType\": \"str\", \"props\": [{\"p\": \"payload\"}, {\"p\": \"topic\", \"vt\": \"str\"}], \"repeat\": \"\", \"topic\": \"get_subscribers\", \"type\": \"inject\", \"wires\": [[\"a695d7033f713e4a\"]], \"x\": 1140, \"y\": 2940, \"z\": \"02bb203de5d55ce5\"}, {\"crontab\": \"\", \"id\": \"2d26b96d9a2cbc8d\", \"name\": \"add_subscription\", \"once\": false, \"onceDelay\": 0.1, \"payload\": \"{\\\"user\\\":3,\\\"list\\\":4}\", \"payloadType\": \"json\", \"props\": [{\"p\": \"payload\"}, {\"p\": \"topic\", \"vt\": \"str\"}], \"repeat\": \"\", \"topic\": \"add_subscription\", \"type\": \"inject\", \"wires\": [[\"a695d7033f713e4a\"]], \"x\": 1080, \"y\": 3100, \"z\": \"02bb203de5d55ce5\"}, {\"crontab\": \"\", \"id\": \"0d034144e9fad648\", \"name\": \"remove_subscription\", \"once\": false, \"onceDelay\": 0.1, \"payload\": \"{\\\"user\\\":3,\\\"list\\\":4}\", \"payloadType\": \"json\", \"props\": [{\"p\": \"payload\"}, {\"p\": \"topic\", \"vt\": \"str\"}], \"repeat\": \"\", \"topic\": \"remove_subscription\", \"type\": \"inject\", \"wires\": [[\"a695d7033f713e4a\"]], \"x\": 1070, \"y\": 3140, \"z\": \"02bb203de5d55ce5\"}, {\"crontab\": \"\", \"id\": \"9c1707deb912fcf8\", \"name\": \"get_subscriber_by_attrib\", \"once\": false, \"onceDelay\": 0.1, \"payload\": \"[\\\"internal_user\\\",1234567890]\", \"payloadType\": \"json\", \"props\": [{\"p\": \"payload\"}, {\"p\": \"topic\", \"vt\": \"str\"}], \"repeat\": \"\", \"topic\": \"get_subscriber_by_attrib\", \"type\": \"inject\", \"wires\": [[\"a695d7033f713e4a\"]], \"x\": 1110, \"y\": 3260, \"z\": \"02bb203de5d55ce5\"}, {\"crontab\": \"\", \"id\": \"3d0cec56c5b275c8\", \"name\": \"delete_subscriber_by_attrib\", \"once\": false, \"onceDelay\": 0.1, \"payload\": \"[\\\"internal_user\\\",1234567890]\", \"payloadType\": \"json\", \"props\": [{\"p\": \"payload\"}, {\"p\": \"topic\", \"vt\": \"str\"}], \"repeat\": \"\", \"topic\": \"delete_subscriber_by_attrib\", \"type\": \"inject\", \"wires\": [[\"a695d7033f713e4a\"]], \"x\": 1100, \"y\": 3300, \"z\": \"02bb203de5d55ce5\"}, {\"crontab\": \"\", \"id\": \"47f34392c4c4b923\", \"name\": \"add_subscriber\", \"once\": false, \"onceDelay\": 0.1, \"payload\": \"{\\\"email\\\":\\\"[email protected]\\\",\\\"name\\\":\\\"test4\\\",\\\"status\\\":\\\"enabled\\\",\\\"lists\\\":[2],\\\"attribs\\\":{\\\"internal_user\\\":\\\"1234567890\\\"},\\\"preconfirm_subscriptions\\\":true}\", \"payloadType\": \"json\", \"props\": [{\"p\": \"payload\"}, {\"p\": \"topic\", \"vt\": \"str\"}], \"repeat\": \"\", \"topic\": \"add_subscriber\", \"type\": \"inject\", \"wires\": [[\"a695d7033f713e4a\"]], \"x\": 1140, \"y\": 3220, \"z\": \"02bb203de5d55ce5\"}, {\"active\": true, \"complete\": \"false\", \"console\": false, \"id\": \"bc6df03166084a21\", \"name\": \"debug 171\", \"statusType\": \"auto\", \"statusVal\": \"\", \"tosidebar\": true, \"tostatus\": false, \"type\": \"debug\", \"wires\": [], \"x\": 1690, \"y\": 3100, \"z\": \"02bb203de5d55ce5\"}, {\"crontab\": \"\", \"id\": \"420850a07614dd09\", \"name\": \"create scheduled campaign\", \"once\": false, \"onceDelay\": 0.1, \"payload\": \"{\\\"name\\\":\\\"Daily Newsletter\\\",\\\"subject\\\":\\\"My news letter\\\",\\\"lists\\\":[1],\\\"body\\\":\\\"<div class='section-title'><h2>Test</h2></div>\\\",\\\"from_email\\\":\\\"Sender <[email protected]>\\\",\\\"type\\\":\\\"regular\\\",\\\"content_type\\\":\\\"html\\\",\\\"template_id\\\":1,\\\"tags\\\":[\\\"maillist\\\"],\\\"send_at\\\":\\\"2025-03-25T10:00:00Z\\\"}\", \"payloadType\": \"json\", \"props\": [{\"p\": \"payload\"}, {\"p\": \"topic\", \"vt\": \"str\"}], \"repeat\": \"\", \"topic\": \"create_campaign\", \"type\": \"inject\", \"wires\": [[\"f6ab457865b2e961\"]], \"x\": 1140, \"y\": 3400, \"z\": \"02bb203de5d55ce5\"}, {\"env\": [{\"name\": \"LISTMONK_API_USER\", \"type\": \"str\", \"value\": \"user\"}, {\"name\": \"LISTMONK_API_KEY\", \"type\": \"env\", \"value\": \"xxxxxxxxxxxxxxxxx\"}, {\"name\": \"COMMAND\", \"type\": \"str\", \"value\": \"create_campaign\"}], \"id\": \"f6ab457865b2e961\", \"name\": \"Create\", \"type\": \"subflow:539a548b549c19dd\", \"wires\": [[\"7822385efcf41610\", \"9757ed4f630165fa\", \"f175cf628d7ef044\"]], \"x\": 1330, \"y\": 3400, \"z\": \"02bb203de5d55ce5\"}, {\"active\": true, \"complete\": \"false\", \"console\": false, \"id\": \"18dbb5da307169cc\", \"name\": \"debug 176\", \"statusType\": \"auto\", \"statusVal\": \"\", \"tosidebar\": true, \"tostatus\": false, \"type\": \"debug\", \"wires\": [], \"x\": 1687, \"y\": 3379, \"z\": \"02bb203de5d55ce5\"}, {\"active\": true, \"complete\": \"false\", \"console\": false, \"id\": \"bfb80a88ec3011ed\", \"name\": \"debug 177\", \"statusType\": \"auto\", \"statusVal\": \"\", \"tosidebar\": true, \"tostatus\": false, \"type\": \"debug\", \"wires\": [], \"x\": 1690, \"y\": 3440, \"z\": \"02bb203de5d55ce5\"}, {\"crontab\": \"\", \"id\": \"5fc9aea7b927d50a\", \"name\": \"create and start campaign\", \"once\": false, \"onceDelay\": 0.1, \"payload\": \"{\\\"name\\\":\\\"Daily Newsletter\\\",\\\"subject\\\":\\\"My news letter\\\",\\\"lists\\\":[1],\\\"body\\\":\\\"<div class='section-title'><h2>Test</h2></div>\\\",\\\"from_email\\\":\\\"Sender <[email protected]>\\\",\\\"type\\\":\\\"regular\\\",\\\"content_type\\\":\\\"html\\\",\\\"template_id\\\":1,\\\"tags\\\":[\\\"maillist\\\"]}\", \"payloadType\": \"json\", \"props\": [{\"p\": \"payload\"}, {\"p\": \"topic\", \"vt\": \"str\"}], \"repeat\": \"\", \"topic\": \"start_campaign\", \"type\": \"inject\", \"wires\": [[\"c4cf33d1546ce804\"]], \"x\": 1110, \"y\": 3540, \"z\": \"02bb203de5d55ce5\"}, {\"active\": true, \"complete\": \"false\", \"console\": false, \"id\": \"46faf6fb1e31620c\", \"name\": \"debug 178\", \"statusType\": \"auto\", \"statusVal\": \"\", \"tosidebar\": true, \"tostatus\": false, \"type\": \"debug\", \"wires\": [], \"x\": 1670, \"y\": 3540, \"z\": \"02bb203de5d55ce5\"}, {\"active\": true, \"complete\": \"false\", \"console\": false, \"id\": \"08a896d7109ffe93\", \"name\": \"debug 179\", \"statusType\": \"auto\", \"statusVal\": \"\", \"tosidebar\": true, \"tostatus\": false, \"type\": \"debug\", \"wires\": [], \"x\": 1498, \"y\": 3499, \"z\": \"02bb203de5d55ce5\"}, {\"active\": true, \"complete\": \"false\", \"console\": false, \"id\": \"7822385efcf41610\", \"name\": \"debug 180\", \"statusType\": \"auto\", \"statusVal\": \"\", \"tosidebar\": true, \"tostatus\": false, \"type\": \"debug\", \"wires\": [], \"x\": 1530, \"y\": 3320, \"z\": \"02bb203de5d55ce5\"}, {\"env\": [{\"name\": \"LISTMONK_API_USER\", \"type\": \"str\", \"value\": \"user\"}, {\"name\": \"LISTMONK_API_KEY\", \"type\": \"env\", \"value\": \"xxxxxxxxxxxxxxxxx\"}, {\"name\": \"COMMAND\", \"type\": \"str\", \"value\": \"test_campaign\"}, {\"name\": \"TEST_EMAILS\", \"type\": \"json\", \"value\": \"[\\\"[email protected]\\\"]\"}], \"id\": \"f175cf628d7ef044\", \"name\": \"Test\", \"type\": \"subflow:539a548b549c19dd\", \"wires\": [[\"bfb80a88ec3011ed\"]], \"x\": 1490, \"y\": 3440, \"z\": \"02bb203de5d55ce5\"}, {\"env\": [{\"name\": \"LISTMONK_API_USER\", \"type\": \"str\", \"value\": \"user\"}, {\"name\": \"LISTMONK_API_KEY\", \"type\": \"env\", \"value\": \"xxxxxxxxxxxxxxxxx\"}, {\"name\": \"COMMAND\", \"type\": \"str\", \"value\": \"create_campaign\"}], \"id\": \"c4cf33d1546ce804\", \"name\": \"Create\", \"type\": \"subflow:539a548b549c19dd\", \"wires\": [[\"08a896d7109ffe93\", \"5a49ca3ca3c5f40d\"]], \"x\": 1330, \"y\": 3540, \"z\": \"02bb203de5d55ce5\"}, {\"env\": [{\"name\": \"LISTMONK_API_USER\", \"type\": \"str\", \"value\": \"user\"}, {\"name\": \"LISTMONK_API_KEY\", \"type\": \"env\", \"value\": \"xxxxxxxxxxxxxxxxx\"}, {\"name\": \"COMMAND\", \"type\": \"str\", \"value\": \"schedule_campaign\"}], \"id\": \"9757ed4f630165fa\", \"name\": \"Schedule\", \"type\": \"subflow:539a548b549c19dd\", \"wires\": [[\"18dbb5da307169cc\"]], \"x\": 1500, \"y\": 3380, \"z\": \"02bb203de5d55ce5\"}, {\"env\": [{\"name\": \"LISTMONK_API_USER\", \"type\": \"str\", \"value\": \"user\"}, {\"name\": \"LISTMONK_API_KEY\", \"type\": \"env\", \"value\": \"xxxxxxxxxxxxxxxxx\"}, {\"name\": \"COMMAND\", \"type\": \"str\", \"value\": \"start_campaign\"}], \"id\": \"5a49ca3ca3c5f40d\", \"name\": \"Start\", \"type\": \"subflow:539a548b549c19dd\", \"wires\": [[\"46faf6fb1e31620c\"]], \"x\": 1490, \"y\": 3540, \"z\": \"02bb203de5d55ce5\"}]\r\n```\r\n\r\n## Key changes and improvements:\r\n\r\n- **Campaign Management Section:** Added a dedicated section for campaign-related commands, improving organization.\r\n- **`TEST_EMAILS` Configuration:** Clearly documented the `TEST_EMAILS` variable, including its type (JSON array) and purpose. This is crucial for the `test_campaign` command.\r\n- **Detailed Command Documentation:** Expanded the documentation for each command, providing:\r\n - Clearer descriptions of input `msg.payload` requirements.\r\n - Specific examples for each command, including JSON payloads where appropriate.\r\n - Links to the relevant ListMonk API documentation where available.\r\n - Notes on specific behaviors (like the subject modification for test campaigns).\r\n- **`create_campaign` Details:** Provided a more detailed example and explanation of the common fields used in the `create_campaign` payload.\r\n- **`schedule_campaign`, `test_campaign`, `start_campaign` Details:** Added payload description and example.\r\n- **Error Handling:** Improved description of the \"unknown command\" error handling. Also added error handling for missing campaign ID.\r\n- **Clarity and Consistency:** Improved the overall clarity and consistency of language and formatting.\r\n- **Removed duplicated example json**: Removed example code from the doc.\r\n\r\n","category":"","in":[{"x":80,"y":400,"wires":[{"id":"e4d6a43404fcd1aa"}]}],"out":[{"x":1970,"y":440,"wires":[{"id":"935b77753e0823cb","port":1},{"id":"19369549187b07c3","port":0}]}],"env":[{"name":"LISTMONK_BASE_URL","type":"str","value":"https://my.listmonk.instance/api","ui":{"label":{"en-US":"Base URL"},"type":"input","opts":{"types":["str","env"]}}},{"name":"LISTMONK_API_USER","type":"str","value":"","ui":{"label":{"en-US":"Api user"},"type":"input","opts":{"types":["str","env"]}}},{"name":"LISTMONK_API_KEY","type":"str","value":"","ui":{"label":{"en-US":"API key"},"type":"input","opts":{"types":["str","env"]}}},{"name":"COMMAND","type":"str","value":"topic","ui":{"label":{"en-US":"Command"},"type":"select","opts":{"opts":[{"l":{"en-US":"from msg.topic"},"v":"topic"},{"l":{"en-US":"get subscribers"},"v":"get_subscribers"},{"l":{"en-US":"get subscriber by id"},"v":"get_subscriber_by_id"},{"l":{"en-US":"get subscriptions by userid"},"v":"get_subscriptions_by_userid"},{"l":{"en-US":"subscription add"},"v":"add_subscription"},{"l":{"en-US":"subscription remove"},"v":"remove_subscription"},{"l":{"en-US":"subscription unsubscribe"},"v":"unsubscribe"},{"l":{"en-US":"subscriber add"},"v":"add_subscriber"},{"l":{"en-US":"subscriber get by attrib"},"v":"get_subscriber_by_attrib"},{"l":{"en-US":"subscriber delete by attrib"},"v":"delete_subscriber_by_attrib"},{"l":{"en-US":"campaign create"},"v":"create_campaign"},{"l":{"en-US":"campaign schedule"},"v":"schedule_campaign"},{"l":{"en-US":"campaign test"},"v":"test_campaign"},{"l":{"en-US":"campaign start"},"v":"start_campaign"},{"l":{"en-US":"send email"},"v":"transactional_send"}]}}},{"name":"TEST_EMAILS","type":"json","value":"[]","ui":{"label":{"en-US":"Test email addresses"},"type":"input","opts":{"types":["json","env"]}}}],"meta":{},"color":"#0055d4","outputLabels":["output"],"icon":"node-red/envelope.svg","status":{"x":1760,"y":200,"wires":[{"id":"46f4b59c2fbfcda0","port":0},{"id":"1826a936f9df6011","port":0},{"id":"39ed109e105c7b62","port":0}]}},{"id":"d6802efb63a3ff91","type":"function","z":"539a548b549c19dd","name":"Get subscribers","func":"const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst command = \"subscribers\";\n\nmsg.url = `${base_url}/${command}?per_page=all`;\n\nconst method = \"GET\";\n\nmsg.method = method;\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":320,"wires":[["18aa558621b3fc09"]]},{"id":"18aa558621b3fc09","type":"function","z":"539a548b549c19dd","name":"Auth","func":"const user = env.get (\"LISTMONK_API_USER\");\nconst token = env.get (\"LISTMONK_API_KEY\");\n\nif (!token || !user) {\n node.error(\"No credentials defined for ListMonk service\");\n return;\n}\n\nmsg.headers = {\n Authorization: `token ${user}:${token}`\n}\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1290,"y":440,"wires":[["e9cd8439e9e33482"]]},{"id":"e9cd8439e9e33482","type":"http request","z":"539a548b549c19dd","name":"","method":"use","ret":"obj","paytoqs":"ignore","url":"","tls":"19e98c3.60b8c74","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":1470,"y":440,"wires":[["935b77753e0823cb","46f4b59c2fbfcda0"]]},{"id":"935b77753e0823cb","type":"switch","z":"539a548b549c19dd","name":"","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"get_subscriptions_by_userid","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":1630,"y":440,"wires":[["19369549187b07c3"],[]],"outputLabels":["get_subscriptions_by_userid","other"]},{"id":"19369549187b07c3","type":"function","z":"539a548b549c19dd","name":"Get subscription list","func":"if (msg.payload.data.lists) {\n msg.payload = msg.payload.data.lists;\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1810,"y":380,"wires":[[]]},{"id":"3307f051db96af20","type":"switch","z":"539a548b549c19dd","name":"command","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"get_subscribers","vt":"str"},{"t":"eq","v":"get_subscriber_by_id","vt":"str"},{"t":"eq","v":"get_subscriptions_by_userid","vt":"str"},{"t":"eq","v":"add_subscription","vt":"str"},{"t":"eq","v":"remove_subscription","vt":"str"},{"t":"eq","v":"unsubscribe","vt":"str"},{"t":"eq","v":"add_subscriber","vt":"str"},{"t":"eq","v":"get_subscriber_by_attrib","vt":"str"},{"t":"eq","v":"delete_subscriber_by_attrib","vt":"str"},{"t":"eq","v":"create_campaign","vt":"str"},{"t":"eq","v":"schedule_campaign","vt":"str"},{"t":"eq","v":"test_campaign","vt":"str"},{"t":"eq","v":"start_campaign","vt":"str"},{"t":"eq","v":"transactional_send","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":15,"x":540,"y":520,"wires":[["d6802efb63a3ff91"],["4b82f73b7d4fbcec"],["ae72fb6d3fe1d894"],["b5cfcbda104dd714"],["719533cf5ca15e07"],["ad71386962404079"],["268ee04c7434de4d"],["e86eb12c15f1889f"],["f687003d0d4665fa"],["b5bb8d6c07a22b04"],["2ab277fe9e949ee8"],["7a4239c193223cb4"],["e3ddea1da6bac262"],["20593fa4fe750c81"],["5b646c17ebe144a2","39ed109e105c7b62"]],"outputLabels":["get_subscriber","get_subscriber_by_id","get_subscriptions_by_userid","add_subscription","remove_subscription","unsubscribe","add_subscriber","get_subscriber_by_attrib","delete_subscriber_by_attrib","create_campaign","schedule_campaign","test_campaign","start_campaign","transactional_send","unknown"]},{"id":"ebf3df5fd3b147c0","type":"inject","z":"539a548b549c19dd","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"COMMAND","payloadType":"env","x":1390,"y":160,"wires":[["1826a936f9df6011"]]},{"id":"46f4b59c2fbfcda0","type":"function","z":"539a548b549c19dd","name":"Request result","func":"msg.payload= {\n text: msg.topic,\n shape: \"dot\"\n}\n\nif (msg.statusCode == 200) {\n msg.payload.fill = \"green\";\n} else {\n msg.payload.fill = \"red\";\n}\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1560,"y":260,"wires":[[]],"outputLabels":["msg"]},{"id":"4b82f73b7d4fbcec","type":"function","z":"539a548b549c19dd","name":"Get subscriber by id","func":"const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst command = \"subscribers\";\n\nmsg.url = `${base_url}/${command}/${msg.payload}`;\n\nconst method = \"GET\";\n\nmsg.method = method;\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":860,"y":360,"wires":[["18aa558621b3fc09"]]},{"id":"ae72fb6d3fe1d894","type":"function","z":"539a548b549c19dd","name":"Get subscriptions by user id","func":"const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst command = \"subscribers\";\n\nmsg.url = `${base_url}/${command}/${msg.payload}`;\n\nconst method = \"GET\";\n\nmsg.method = method;\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":880,"y":400,"wires":[["18aa558621b3fc09"]]},{"id":"b5cfcbda104dd714","type":"function","z":"539a548b549c19dd","name":"Add subscription to user","func":"const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst command = \"subscribers/lists\";\n\nmsg.url = `${base_url}/${command}`;\n\nconst method = \"PUT\";\n\nmsg.method = method;\n\nmsg.payload = {\n ids: [msg.payload.user],\n action: \"add\",\n target_list_ids: [msg.payload.list],\n status: \"confirmed\"\n}\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":870,"y":480,"wires":[["18aa558621b3fc09"]]},{"id":"719533cf5ca15e07","type":"function","z":"539a548b549c19dd","name":"Remove subscription from user","func":"const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst command = \"subscribers/lists\";\n\nmsg.url = `${base_url}/${command}`;\n\nconst method = \"PUT\";\n\nmsg.method = method;\n\nmsg.payload = {\n ids: [msg.payload.user],\n action: \"remove\",\n target_list_ids: [msg.payload.list],\n status: \"confirmed\"\n}\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":890,"y":520,"wires":[["18aa558621b3fc09"]]},{"id":"ad71386962404079","type":"function","z":"539a548b549c19dd","name":"User unsubscribe subscription","func":"const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst command = \"subscribers/lists\";\n\nmsg.url = `${base_url}/${command}`;\n\nconst method = \"PUT\";\n\nmsg.method = method;\n\nmsg.payload = {\n ids: [msg.payload.user],\n action: \"unsubscribe\",\n target_list_ids: [msg.payload.list],\n status: \"confirmed\"\n}\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":890,"y":560,"wires":[["18aa558621b3fc09"]]},{"id":"268ee04c7434de4d","type":"function","z":"539a548b549c19dd","name":"Add subscriber","func":"const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst command = \"subscribers\";\n\nmsg.url = `${base_url}/${command}`;\n\nconst method = \"POST\";\n\nmsg.method = method;\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":600,"wires":[["18aa558621b3fc09"]]},{"id":"f687003d0d4665fa","type":"function","z":"539a548b549c19dd","name":"Delete subscriber by tinygs user","func":"const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst key = msg.payload[0];\nconst value = msg.payload[1];\n\nconst query = `subscribers.attribs->>'${key}'='${value}'`;\n\nmsg.payload = {\n query: query\n}\n\nconst command = \"subscribers/query/delete\";\n\nmsg.url = `${base_url}/${command}`;\n\nconst method = \"POST\";\n\nmsg.method = method;\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":890,"y":680,"wires":[["18aa558621b3fc09"]]},{"id":"e4d6a43404fcd1aa","type":"switch","z":"539a548b549c19dd","name":"","property":"COMMAND","propertyType":"env","rules":[{"t":"eq","v":"topic","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":230,"y":400,"wires":[["3307f051db96af20"],["870a592b90cdadf0"]],"outputLabels":["from topic","command"]},{"id":"870a592b90cdadf0","type":"function","z":"539a548b549c19dd","name":"from command","func":"msg.topic = env.get(\"COMMAND\");\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":300,"y":520,"wires":[["3307f051db96af20"]]},{"id":"5b646c17ebe144a2","type":"function","z":"539a548b549c19dd","d":true,"name":"unknown command","func":"msg.payload = {\n result: `unknown command: ${msg.topic}`,\n original_data: msg.payload\n};\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":270,"y":640,"wires":[[]]},{"id":"39ed109e105c7b62","type":"function","z":"539a548b549c19dd","name":"Unknown command","func":"msg.payload= {\n text: `unknown: ${msg.topic}`,\n shape: \"dot\",\n fill: \"red\"\n}\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":660,"y":280,"wires":[[]],"outputLabels":["msg"]},{"id":"1826a936f9df6011","type":"function","z":"539a548b549c19dd","name":"Command","func":"msg.payload= {\n text: msg.payload,\n shape: \"dot\",\n fill: \"grey\"\n}\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1570,"y":160,"wires":[[]],"outputLabels":["msg"]},{"id":"e86eb12c15f1889f","type":"function","z":"539a548b549c19dd","name":"Get subscriber by attrib","func":"const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst key = msg.payload[0];\nconst value = msg.payload[1];\n\nconst query = `subscribers.attribs->>'${key}'='${value}'`;\n\nconst command = \"subscribers\";\n\nmsg.url = `${base_url}/${command}?query=${query}`;\n\nconst method = \"GET\";\n\nmsg.method = method;\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":870,"y":640,"wires":[["18aa558621b3fc09"]]},{"id":"9a05d7fbd929753a","type":"comment","z":"539a548b549c19dd","name":"Output2","info":"","x":470,"y":640,"wires":[]},{"id":"b5bb8d6c07a22b04","type":"function","z":"539a548b549c19dd","name":"Create Campaign","func":"const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst command = \"campaigns\";\n\nmsg.url = `${base_url}/${command}`;\n\nconst method = \"POST\";\n\nmsg.method = method;\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":850,"y":720,"wires":[["18aa558621b3fc09"]]},{"id":"2ab277fe9e949ee8","type":"function","z":"539a548b549c19dd","name":"Schedule Campaign","func":"const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst id = msg.payload.data.id ?? null;\nif (!id) {\n node.error(\"No campaign id provided\", msg);\n return;\n}\n\nconst command = `campaigns/${id}/status`;\n\nmsg.url = `${base_url}/${command}`;\n\nconst method = \"PUT\";\n\nmsg.method = method;\n\nmsg.payload = {\n status: \"scheduled\"\n};\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":860,"y":760,"wires":[["18aa558621b3fc09"]]},{"id":"7a4239c193223cb4","type":"function","z":"539a548b549c19dd","name":"Test Campaign","func":"const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst id = msg.payload.data.id ?? null;\nif (!id) {\n node.error(\"No campaign id provided\", msg);\n return;\n}\n\nconst command = `campaigns/${id}/test`;\n\nmsg.url = `${base_url}/${command}`;\n\nconst method = \"POST\";\n\nmsg.method = method;\n\nlet emails = env.get(\"TEST_EMAILS\");\n\nif (typeof emails !== 'string') {\n emails = String(emails || ''); // Convierte a cadena o inicializa vacía\n}\n\n// Convertir la cadena en un array\nconst emailArray = emails\n .split(',') // Divide por comas\n .map(email => email.trim()) // Elimina espacios en blanco\n .filter(email => email !== ''); // Elimina entradas vacías\n\nmsg.payload = {\n subscribers: emailArray,\n name: msg.payload.data.name,\n subject: `${msg.payload.data.subject} (test)`,\n lists: [msg.payload.data.lists[0].id],\n from_email: msg.payload.data.from_email,\n type: msg.payload.data.type,\n content_type: msg.payload.data.content_type,\n body: msg.payload.data.body,\n template_id: msg.payload.data.template_id,\n messenger: msg.payload.data.messenger,\n tags: msg.payload.data.tags\n};\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":800,"wires":[["18aa558621b3fc09"]]},{"id":"e3ddea1da6bac262","type":"function","z":"539a548b549c19dd","name":"Start Campaign","func":"const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst id = msg.payload.data.id ?? null;\nif (!id) {\n node.error(\"No campaign id provided\", msg);\n return;\n}\n\nconst command = `campaigns/${id}/status`;\n\nmsg.url = `${base_url}/${command}`;\n\nconst method = \"PUT\";\n\nmsg.method = method;\n\nmsg.payload = {\n status: \"running\"\n};\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":840,"wires":[["18aa558621b3fc09"]]},{"id":"20593fa4fe750c81","type":"function","z":"539a548b549c19dd","name":"transactional_send","func":"const base_url = env.get (\"LISTMONK_BASE_URL\");\n\nif (!base_url) {\n node.error(\"No base URL defined for ListMonk service\");\n return;\n}\n\nconst id = msg.payload.data ?? null;\nif (!id) {\n node.error(\"No content provided\", msg);\n return;\n}\n\nconst command = `tx`;\n\nmsg.url = `${base_url}/${command}`;\n\nconst method = \"POST\";\n\nmsg.method = method;\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":850,"y":880,"wires":[["18aa558621b3fc09"]]},{"id":"19e98c3.60b8c74","type":"tls-config","name":"default","cert":"","key":"","ca":"","certname":"","keyname":"","caname":"","servername":"","verifyservercert":true},{"id":"a695d7033f713e4a","type":"subflow:539a548b549c19dd","z":"02bb203de5d55ce5","name":"","env":[{"name":"LISTMONK_API_USER","value":"my_api_user","type":"str"},{"name":"LISTMONK_API_KEY","value":"xxxxxxxxxxxxxxxxx","type":"str"}],"x":1560,"y":3120,"wires":[["bc6df03166084a21"]]}]