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 the COMMAND variable. See the 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.
  • 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.

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. For get_subscriptions_by_userid, the subflow extracts the lists array from the response data and sets it as msg.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" and msg.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 the lists data.
  • Example: Send a message with msg.topic = "get_subscriptions_by_userid" and msg.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" and msg.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" and msg.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" and msg.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 need email, name, and lists (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" and msg.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" and msg.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 or optin).
    • content_type (string): html or plain.
    • 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 a data field. Inside must contain id 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 a data 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 contain id 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 or plain).
    • 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"]]}]

Flow Info

Created 2 months, 2 weeks ago
Rating: not yet rated

Owner

Actions

Rate:

Node Types

Core
  • comment (x1)
  • function (x21)
  • http request (x1)
  • inject (x1)
  • switch (x3)
  • tls-config (x1)
Other
  • subflow (x1)
  • subflow:539a548b549c19dd (x1)

Tags

  • mail
  • listmonk
  • maillist
  • mailing
  • newsletter
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option