n8n-nodes-mautic-advanced 0.3.8 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,335 +1,335 @@
1
- # n8n Mautic Advanced Node
2
-
3
- [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-Support-yellow.svg)](https://buymeacoffee.com/msoukhomlinov)
4
-
5
- Enhanced n8n node for Mautic with comprehensive API coverage including tags, campaigns, categories, notifications, and advanced contact management.
6
-
7
- ## 📋 Table of Contents
8
-
9
- - [What Makes This "Advanced"?](#what-makes-this-advanced)
10
- - [Features](#features)
11
- - [Supported Resources and Operations](#supported-resources-and-operations)
12
- - [Installation](#installation)
13
- - [Authentication](#authentication)
14
- - [Advanced Features](#advanced-features)
15
- - [Usage Examples](#usage-examples)
16
- - [Development](#development)
17
- - [Troubleshooting](#troubleshooting)
18
- - [Changelog](#changelog)
19
- - [Support](#support)
20
- - [License](#license)
21
-
22
- ## What Makes This "Advanced"?
23
-
24
- This enhanced version extends the standard n8n Mautic node with:
25
-
26
- - **🏷️ Complete Tag Management**: Full CRUD operations for tags (missing in the standard node)
27
- - **📊 Campaign Operations**: Create, clone, update, and manage campaigns
28
- - **📁 Category Management**: Handle categories with bundle and color support
29
- - **🔔 Notification Management**: Full CRUD operations for notifications with scheduling and language support
30
- - **🔗 Advanced Relationship Management**: Sophisticated contact-to-campaign and contact-to-company associations
31
- - **📧 Enhanced Email Operations**: Segment-based email sending capabilities
32
- - **👥 Extended Contact Operations**: UTM tag management, activity tracking, device information, and notes
33
- - **🏢 Complete Company Management**: Full company lifecycle with custom fields and address support
34
- - **🔍 Advanced Filtering**: Where filters, DNC filtering, and field selection
35
- - **📅 Smart Date Handling**: Automatic date formatting for Mautic API compatibility
36
-
37
- ## Features
38
-
39
- ### 🚀 Core Features
40
- - **Comprehensive API Coverage**: All major Mautic API endpoints supported
41
- - **Advanced Filtering**: Where filters with nested conditions (andX/orX)
42
- - **DNC Management**: Filter contacts by Do Not Contact status
43
- - **Field Selection**: Choose which fields to return for Contact operations
44
- - **Pagination Support**: Automatic handling of large datasets
45
- - **Custom Fields**: Full support for custom field management
46
- - **Error Handling**: Robust error handling and validation
47
-
48
- ### 🔐 Authentication
49
- - **API Credentials**: Simple API key authentication
50
- - **OAuth2**: Full OAuth2 flow support for secure authentication
51
-
52
- ### 📊 Data Management
53
- - **RAW Data Options**: Control data output format
54
- - **System Fields**: Built-in support for system fields
55
- - **Date Formatting**: Automatic UTC date formatting
56
- - **Deduplication**: Prevents duplicate records in paginated results
57
-
58
- ## Supported Resources and Operations
59
-
60
- ### 🏢 Companies
61
- - **Create** a new company with full address and custom field support
62
- - **Get** a company by ID
63
- - **Get Many** companies with filtering and pagination
64
- - **Update** company details
65
- - **Delete** a company
66
-
67
- ### 👥 Contacts (Enhanced)
68
- - **Create** a new contact with extensive field options
69
- - **Get** a contact by ID with field selection
70
- - **Get Many** contacts with advanced filtering and DNC options
71
- - **Update** contact details
72
- - **Delete** a contact
73
- - **Delete Batch** multiple contacts in one operation
74
- - **Send Email** to a contact
75
- - **Edit Contact Points** (add/subtract points)
76
- - **Edit Do Not Contact List** (add/remove from DNC)
77
- - **Add/Remove UTM Tags** for tracking
78
- - **Get Notes** associated with a contact
79
- - **Get Activity** history for a contact
80
- - **Get Companies** associated with a contact
81
- - **Get Devices** used by a contact
82
-
83
- ### 🏷️ Tags
84
- - **Create** a new tag with description
85
- - **Get** a tag by ID
86
- - **Get Many** tags with search capabilities
87
- - **Update** tag name and description
88
- - **Delete** a tag
89
-
90
- ### 📊 Campaigns
91
- - **Create** a new campaign
92
- - **Get** a campaign by ID
93
- - **Get All** campaigns
94
- - **Update** campaign details
95
- - **Delete** a campaign
96
- - **Clone** an existing campaign
97
- - **Get Contacts** in a campaign
98
-
99
- ### 📁 Categories
100
- - **Create** a new category with bundle and color settings
101
- - **Get** a category by ID
102
- - **Get Many** categories
103
- - **Update** category details
104
- - **Delete** a category
105
-
106
- ### 🔔 Notifications
107
- - **Create** a new notification with scheduling and language support
108
- - **Get** a notification by ID
109
- - **Get Many** notifications with filtering
110
- - **Update** notification details
111
- - **Delete** a notification
112
-
113
- ### 📋 Segments
114
- - **Create** a new segment
115
- - **Get** a segment by ID
116
- - **Get Many** segments with filtering
117
- - **Update** segment details
118
- - **Delete** a segment
119
-
120
- ### 🔗 Relationship Management
121
- - **Campaign Contact**: Add/remove contacts to/from campaigns
122
- - **Company Contact**: Add/remove contacts to/from companies
123
- - **Contact Segment**: Add/remove contacts to/from segments
124
-
125
- ### 📧 Email Operations
126
- - **Segment Email**: Send emails to segments
127
-
128
- ## Installation
129
-
130
- ### Method 1: npm (Recommended)
131
- ```bash
132
- npm install n8n-nodes-mautic-advanced
133
- ```
134
-
135
- ### Method 2: Manual Installation
136
- 1. Clone this repository:
137
- ```bash
138
- git clone https://github.com/msoukhomlinov/n8n-nodes-mautic-advanced.git
139
- cd n8n-nodes-mautic-advanced
140
- ```
141
-
142
- 2. Install dependencies:
143
- ```bash
144
- npm install
145
- ```
146
-
147
- 3. Build the node:
148
- ```bash
149
- npm run build
150
- ```
151
-
152
- 4. Link to your n8n installation:
153
- ```bash
154
- npm link
155
- cd /path/to/your/n8n/installation
156
- npm link n8n-nodes-mautic-advanced
157
- ```
158
-
159
- ## Authentication
160
-
161
- ### API Credentials
162
- 1. Go to your Mautic instance
163
- 2. Navigate to **Settings** → **API Credentials**
164
- 3. Create a new API credential
165
- 4. Copy the **Public Key** and **Secret Key**
166
- 5. In n8n, add a new Mautic Advanced credential
167
- 6. Select **Credentials** authentication method
168
- 7. Enter your Mautic URL, Public Key, and Secret Key
169
-
170
- ### OAuth2
171
- 1. In n8n, add a new Mautic Advanced credential
172
- 2. Select **OAuth2** authentication method
173
- 3. Enter your Mautic URL
174
- 4. Follow the OAuth2 authorization flow
175
-
176
- ## Advanced Features
177
-
178
- ### Where Filters
179
- Advanced filtering for Contact > Get Many operations:
180
- - **Nested Conditions**: Support for andX/orX logical operators
181
- - **Multiple Expressions**: eq, neq, lt, lte, gt, gte, between, in, isNull, isNotNull
182
- - **Custom Fields**: Filter by any custom or system field
183
- - **Date Filtering**: Automatic date formatting for Mautic API
184
-
185
- ### DNC Filtering
186
- Filter contacts by Do Not Contact status:
187
- - **Email DNC Only**: Contacts with email DNC enabled
188
- - **SMS DNC Only**: Contacts with SMS DNC enabled
189
- - **Any DNC Only**: Contacts with any DNC enabled
190
-
191
- ### Field Selection
192
- Choose which fields to return for Contact operations:
193
- - **System Fields**: date_added, date_modified, id, owner_id
194
- - **Custom Fields**: Any custom field defined in Mautic
195
- - **All Fields**: Return complete contact data
196
-
197
- ### Date Formatting
198
- Automatic date formatting for known date fields:
199
- - **Format**: YYYY-MM-DD HH:mm:ss UTC
200
- - **Compatibility**: Ensures Mautic API compatibility
201
- - **Fields**: date_added, date_modified, lastActive, etc.
202
-
203
- ## Usage Examples
204
-
205
- ### Create a Contact with Tags
206
- ```javascript
207
- // Contact Create operation
208
- {
209
- "email": "john.doe@example.com",
210
- "firstName": "John",
211
- "lastName": "Doe",
212
- "additionalFields": {
213
- "tags": ["customer", "vip"],
214
- "company": "Example Corp",
215
- "phone": "+1234567890"
216
- }
217
- }
218
- ```
219
-
220
- ### Filter Contacts with Where Conditions
221
- ```javascript
222
- // Contact Get Many with Where filter
223
- {
224
- "where": {
225
- "conditions": [
226
- {
227
- "col": "email",
228
- "expr": "neq",
229
- "val": ""
230
- },
231
- {
232
- "col": "date_added",
233
- "expr": "gte",
234
- "val": "2024-01-01"
235
- }
236
- ]
237
- }
238
- }
239
- ```
240
-
241
- ### Send Email to Segment
242
- ```javascript
243
- // Segment Email operation
244
- {
245
- "segmentId": "123",
246
- "emailId": "456",
247
- "options": {
248
- "sendToNewOnly": true
249
- }
250
- }
251
- ```
252
-
253
- ## Development
254
-
255
- ### Prerequisites
256
- - Node.js 16+
257
- - npm or yarn
258
- - n8n development environment
259
-
260
- ### Commands
261
- ```bash
262
- # Install dependencies
263
- npm install
264
-
265
- # Build the node
266
- npm run build
267
-
268
- # Watch for changes (development)
269
- npm run dev
270
-
271
- # Check for linting errors
272
- npm run lint
273
-
274
- # Fix linting errors
275
- npm run lint:fix
276
-
277
- # Format code
278
- npm run format
279
- ```
280
-
281
- ### Project Structure
282
- ```
283
- ├── credentials/ # Authentication credentials
284
- ├── nodes/ # Node implementations
285
- │ └── MauticAdvanced/ # Main node files
286
- ├── dist/ # Compiled output
287
- ├── package.json # Project configuration
288
- └── README.md # This file
289
- ```
290
-
291
- ## Troubleshooting
292
-
293
- ### Common Issues
294
-
295
- #### "Could not get parameter 'options'" Error
296
- **Cause**: Missing Options parameter in node definition
297
- **Solution**: Update to latest version (0.3.2+) which includes all required Options parameters
298
-
299
- #### Authentication Errors
300
- **Cause**: Incorrect credentials or URL
301
- **Solution**:
302
- - Verify Mautic URL format (https://your-mautic.com)
303
- - Check API credentials are active
304
- - Ensure proper permissions for API access
305
-
306
- #### Date Filter Issues
307
- **Cause**: Incorrect date format
308
- **Solution**: Use YYYY-MM-DD format for date filters
309
-
310
- #### Pagination Problems
311
- **Cause**: Large datasets causing timeouts
312
- **Solution**: Use "Return All" option or set appropriate limits
313
-
314
- ### Getting Help
315
- 1. Check the [Changelog](CHANGELOG.md) for recent fixes
316
- 2. Search existing [Issues](https://github.com/msoukhomlinov/n8n-nodes-mautic-advanced/issues)
317
- 3. Create a new issue with detailed information
318
-
319
- ## Support
320
-
321
- If you find this node helpful and want to support its ongoing development, you can buy me a coffee:
322
-
323
- [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-Support-yellow.svg)](https://buymeacoffee.com/msoukhomlinov)
324
-
325
- Your support helps maintain this project and develop new features.
326
-
327
- ## License
328
-
329
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
330
-
331
- ## Credits
332
-
333
- - Built with [n8n](https://n8n.io/) workflow automation platform
334
- - Uses [change-case](https://github.com/blakeembrey/change-case) for string manipulation
335
- - Icons and design inspired by n8n community standards
1
+ # n8n Mautic Advanced Node
2
+
3
+ [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-Support-yellow.svg)](https://buymeacoffee.com/msoukhomlinov)
4
+
5
+ Enhanced n8n node for Mautic with comprehensive API coverage including tags, campaigns, categories, notifications, and advanced contact management.
6
+
7
+ ## 📋 Table of Contents
8
+
9
+ - [What Makes This "Advanced"?](#what-makes-this-advanced)
10
+ - [Features](#features)
11
+ - [Supported Resources and Operations](#supported-resources-and-operations)
12
+ - [Installation](#installation)
13
+ - [Authentication](#authentication)
14
+ - [Advanced Features](#advanced-features)
15
+ - [Usage Examples](#usage-examples)
16
+ - [Development](#development)
17
+ - [Troubleshooting](#troubleshooting)
18
+ - [Changelog](#changelog)
19
+ - [Support](#support)
20
+ - [License](#license)
21
+
22
+ ## What Makes This "Advanced"?
23
+
24
+ This enhanced version extends the standard n8n Mautic node with:
25
+
26
+ - **🏷️ Complete Tag Management**: Full CRUD operations for tags (missing in the standard node)
27
+ - **📊 Campaign Operations**: Create, clone, update, and manage campaigns
28
+ - **📁 Category Management**: Handle categories with bundle and color support
29
+ - **🔔 Notification Management**: Full CRUD operations for notifications with scheduling and language support
30
+ - **🔗 Advanced Relationship Management**: Sophisticated contact-to-campaign and contact-to-company associations
31
+ - **📧 Enhanced Email Operations**: Segment-based email sending capabilities
32
+ - **👥 Extended Contact Operations**: UTM tag management, activity tracking, device information, and notes
33
+ - **🏢 Complete Company Management**: Full company lifecycle with custom fields and address support
34
+ - **🔍 Advanced Filtering**: Where filters, DNC filtering, and field selection
35
+ - **📅 Smart Date Handling**: Automatic date formatting for Mautic API compatibility
36
+
37
+ ## Features
38
+
39
+ ### 🚀 Core Features
40
+ - **Comprehensive API Coverage**: All major Mautic API endpoints supported
41
+ - **Advanced Filtering**: Where filters with nested conditions (andX/orX)
42
+ - **DNC Management**: Filter contacts by Do Not Contact status
43
+ - **Field Selection**: Choose which fields to return for Contact operations
44
+ - **Pagination Support**: Automatic handling of large datasets
45
+ - **Custom Fields**: Full support for custom field management
46
+ - **Error Handling**: Robust error handling and validation
47
+
48
+ ### 🔐 Authentication
49
+ - **API Credentials**: Simple API key authentication
50
+ - **OAuth2**: Full OAuth2 flow support for secure authentication
51
+
52
+ ### 📊 Data Management
53
+ - **RAW Data Options**: Control data output format
54
+ - **System Fields**: Built-in support for system fields
55
+ - **Date Formatting**: Automatic UTC date formatting
56
+ - **Deduplication**: Prevents duplicate records in paginated results
57
+
58
+ ## Supported Resources and Operations
59
+
60
+ ### 🏢 Companies
61
+ - **Create** a new company with full address and custom field support
62
+ - **Get** a company by ID
63
+ - **Get Many** companies with filtering and pagination
64
+ - **Update** company details
65
+ - **Delete** a company
66
+
67
+ ### 👥 Contacts (Enhanced)
68
+ - **Create** a new contact with extensive field options
69
+ - **Get** a contact by ID with field selection
70
+ - **Get Many** contacts with advanced filtering and DNC options
71
+ - **Update** contact details
72
+ - **Delete** a contact
73
+ - **Delete Batch** multiple contacts in one operation
74
+ - **Send Email** to a contact
75
+ - **Edit Contact Points** (add/subtract points)
76
+ - **Edit Do Not Contact List** (add/remove from DNC)
77
+ - **Add/Remove UTM Tags** for tracking
78
+ - **Get Notes** associated with a contact
79
+ - **Get Activity** history for a contact
80
+ - **Get Companies** associated with a contact
81
+ - **Get Devices** used by a contact
82
+
83
+ ### 🏷️ Tags
84
+ - **Create** a new tag with description
85
+ - **Get** a tag by ID
86
+ - **Get Many** tags with search capabilities
87
+ - **Update** tag name and description
88
+ - **Delete** a tag
89
+
90
+ ### 📊 Campaigns
91
+ - **Create** a new campaign
92
+ - **Get** a campaign by ID
93
+ - **Get All** campaigns
94
+ - **Update** campaign details
95
+ - **Delete** a campaign
96
+ - **Clone** an existing campaign
97
+ - **Get Contacts** in a campaign
98
+
99
+ ### 📁 Categories
100
+ - **Create** a new category with bundle and color settings
101
+ - **Get** a category by ID
102
+ - **Get Many** categories
103
+ - **Update** category details
104
+ - **Delete** a category
105
+
106
+ ### 🔔 Notifications
107
+ - **Create** a new notification with scheduling and language support
108
+ - **Get** a notification by ID
109
+ - **Get Many** notifications with filtering
110
+ - **Update** notification details
111
+ - **Delete** a notification
112
+
113
+ ### 📋 Segments
114
+ - **Create** a new segment
115
+ - **Get** a segment by ID
116
+ - **Get Many** segments with filtering
117
+ - **Update** segment details
118
+ - **Delete** a segment
119
+
120
+ ### 🔗 Relationship Management
121
+ - **Campaign Contact**: Add/remove contacts to/from campaigns
122
+ - **Company Contact**: Add/remove contacts to/from companies
123
+ - **Contact Segment**: Add/remove contacts to/from segments
124
+
125
+ ### 📧 Email Operations
126
+ - **Segment Email**: Send emails to segments
127
+
128
+ ## Installation
129
+
130
+ ### Method 1: npm (Recommended)
131
+ ```bash
132
+ npm install n8n-nodes-mautic-advanced
133
+ ```
134
+
135
+ ### Method 2: Manual Installation
136
+ 1. Clone this repository:
137
+ ```bash
138
+ git clone https://github.com/msoukhomlinov/n8n-nodes-mautic-advanced.git
139
+ cd n8n-nodes-mautic-advanced
140
+ ```
141
+
142
+ 2. Install dependencies:
143
+ ```bash
144
+ npm install
145
+ ```
146
+
147
+ 3. Build the node:
148
+ ```bash
149
+ npm run build
150
+ ```
151
+
152
+ 4. Link to your n8n installation:
153
+ ```bash
154
+ npm link
155
+ cd /path/to/your/n8n/installation
156
+ npm link n8n-nodes-mautic-advanced
157
+ ```
158
+
159
+ ## Authentication
160
+
161
+ ### API Credentials
162
+ 1. Go to your Mautic instance
163
+ 2. Navigate to **Settings** → **API Credentials**
164
+ 3. Create a new API credential
165
+ 4. Copy the **Public Key** and **Secret Key**
166
+ 5. In n8n, add a new Mautic Advanced credential
167
+ 6. Select **Credentials** authentication method
168
+ 7. Enter your Mautic URL, Public Key, and Secret Key
169
+
170
+ ### OAuth2
171
+ 1. In n8n, add a new Mautic Advanced credential
172
+ 2. Select **OAuth2** authentication method
173
+ 3. Enter your Mautic URL
174
+ 4. Follow the OAuth2 authorization flow
175
+
176
+ ## Advanced Features
177
+
178
+ ### Where Filters
179
+ Advanced filtering for Contact > Get Many operations:
180
+ - **Nested Conditions**: Support for andX/orX logical operators
181
+ - **Multiple Expressions**: eq, neq, lt, lte, gt, gte, between, in, isNull, isNotNull
182
+ - **Custom Fields**: Filter by any custom or system field
183
+ - **Date Filtering**: Automatic date formatting for Mautic API
184
+
185
+ ### DNC Filtering
186
+ Filter contacts by Do Not Contact status:
187
+ - **Email DNC Only**: Contacts with email DNC enabled
188
+ - **SMS DNC Only**: Contacts with SMS DNC enabled
189
+ - **Any DNC Only**: Contacts with any DNC enabled
190
+
191
+ ### Field Selection
192
+ Choose which fields to return for Contact operations:
193
+ - **System Fields**: date_added, date_modified, id, owner_id
194
+ - **Custom Fields**: Any custom field defined in Mautic
195
+ - **All Fields**: Return complete contact data
196
+
197
+ ### Date Formatting
198
+ Automatic date formatting for known date fields:
199
+ - **Format**: YYYY-MM-DD HH:mm:ss UTC
200
+ - **Compatibility**: Ensures Mautic API compatibility
201
+ - **Fields**: date_added, date_modified, lastActive, etc.
202
+
203
+ ## Usage Examples
204
+
205
+ ### Create a Contact with Tags
206
+ ```javascript
207
+ // Contact Create operation
208
+ {
209
+ "email": "john.doe@example.com",
210
+ "firstName": "John",
211
+ "lastName": "Doe",
212
+ "additionalFields": {
213
+ "tags": ["customer", "vip"],
214
+ "company": "Example Corp",
215
+ "phone": "+1234567890"
216
+ }
217
+ }
218
+ ```
219
+
220
+ ### Filter Contacts with Where Conditions
221
+ ```javascript
222
+ // Contact Get Many with Where filter
223
+ {
224
+ "where": {
225
+ "conditions": [
226
+ {
227
+ "col": "email",
228
+ "expr": "neq",
229
+ "val": ""
230
+ },
231
+ {
232
+ "col": "date_added",
233
+ "expr": "gte",
234
+ "val": "2024-01-01"
235
+ }
236
+ ]
237
+ }
238
+ }
239
+ ```
240
+
241
+ ### Send Email to Segment
242
+ ```javascript
243
+ // Segment Email operation
244
+ {
245
+ "segmentId": "123",
246
+ "emailId": "456",
247
+ "options": {
248
+ "sendToNewOnly": true
249
+ }
250
+ }
251
+ ```
252
+
253
+ ## Development
254
+
255
+ ### Prerequisites
256
+ - Node.js 16+
257
+ - npm or yarn
258
+ - n8n development environment
259
+
260
+ ### Commands
261
+ ```bash
262
+ # Install dependencies
263
+ npm install
264
+
265
+ # Build the node
266
+ npm run build
267
+
268
+ # Watch for changes (development)
269
+ npm run dev
270
+
271
+ # Check for linting errors
272
+ npm run lint
273
+
274
+ # Fix linting errors
275
+ npm run lint:fix
276
+
277
+ # Format code
278
+ npm run format
279
+ ```
280
+
281
+ ### Project Structure
282
+ ```
283
+ ├── credentials/ # Authentication credentials
284
+ ├── nodes/ # Node implementations
285
+ │ └── MauticAdvanced/ # Main node files
286
+ ├── dist/ # Compiled output
287
+ ├── package.json # Project configuration
288
+ └── README.md # This file
289
+ ```
290
+
291
+ ## Troubleshooting
292
+
293
+ ### Common Issues
294
+
295
+ #### "Could not get parameter 'options'" Error
296
+ **Cause**: Missing Options parameter in node definition
297
+ **Solution**: Update to latest version (0.3.2+) which includes all required Options parameters
298
+
299
+ #### Authentication Errors
300
+ **Cause**: Incorrect credentials or URL
301
+ **Solution**:
302
+ - Verify Mautic URL format (https://your-mautic.com)
303
+ - Check API credentials are active
304
+ - Ensure proper permissions for API access
305
+
306
+ #### Date Filter Issues
307
+ **Cause**: Incorrect date format
308
+ **Solution**: Use YYYY-MM-DD format for date filters
309
+
310
+ #### Pagination Problems
311
+ **Cause**: Large datasets causing timeouts
312
+ **Solution**: Use "Return All" option or set appropriate limits
313
+
314
+ ### Getting Help
315
+ 1. Check the [Changelog](CHANGELOG.md) for recent fixes
316
+ 2. Search existing [Issues](https://github.com/msoukhomlinov/n8n-nodes-mautic-advanced/issues)
317
+ 3. Create a new issue with detailed information
318
+
319
+ ## Support
320
+
321
+ If you find this node helpful and want to support its ongoing development, you can buy me a coffee:
322
+
323
+ [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-Support-yellow.svg)](https://buymeacoffee.com/msoukhomlinov)
324
+
325
+ Your support helps maintain this project and develop new features.
326
+
327
+ ## License
328
+
329
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
330
+
331
+ ## Credits
332
+
333
+ - Built with [n8n](https://n8n.io/) workflow automation platform
334
+ - Uses [change-case](https://github.com/blakeembrey/change-case) for string manipulation
335
+ - Icons and design inspired by n8n community standards
@@ -118,6 +118,13 @@ exports.campaignFields = [
118
118
  default: '',
119
119
  description: 'Date/time when the campaign should be published',
120
120
  },
121
+ {
122
+ displayName: 'Alias',
123
+ name: 'alias',
124
+ type: 'string',
125
+ default: '',
126
+ description: 'Used to generate the URL for the campaign',
127
+ },
121
128
  ],
122
129
  },
123
130
  /* -------------------------------------------------------------------------- */
@@ -136,6 +143,19 @@ exports.campaignFields = [
136
143
  },
137
144
  default: '',
138
145
  },
146
+ {
147
+ displayName: 'Create If Not Found',
148
+ name: 'createIfNotFound',
149
+ type: 'boolean',
150
+ displayOptions: {
151
+ show: {
152
+ resource: ['campaign'],
153
+ operation: ['update'],
154
+ },
155
+ },
156
+ default: false,
157
+ description: 'Create a new campaign if the given ID does not exist (uses PUT instead of PATCH)',
158
+ },
139
159
  {
140
160
  displayName: 'Update Fields',
141
161
  name: 'updateFields',
@@ -184,6 +204,13 @@ exports.campaignFields = [
184
204
  default: '',
185
205
  description: 'Date/time when the campaign should be published',
186
206
  },
207
+ {
208
+ displayName: 'Alias',
209
+ name: 'alias',
210
+ type: 'string',
211
+ default: '',
212
+ description: 'Used to generate the URL for the campaign',
213
+ },
187
214
  ],
188
215
  },
189
216
  /* -------------------------------------------------------------------------- */
@@ -139,6 +139,18 @@ exports.contactOperations = [
139
139
  description: 'Get activity events for all contacts',
140
140
  action: 'Get activity events for all contacts',
141
141
  },
142
+ {
143
+ name: 'Get Owners',
144
+ value: 'getOwners',
145
+ description: 'Get list of available owners',
146
+ action: 'Get list of available owners',
147
+ },
148
+ {
149
+ name: 'Get Fields',
150
+ value: 'getFields',
151
+ description: 'Get list of available contact fields',
152
+ action: 'Get list of available contact fields',
153
+ },
142
154
  ],
143
155
  default: 'create',
144
156
  },
@@ -1,10 +1,10 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="349.779" height="349.779">
2
- <path
3
- fill="#FFF"
4
- d="M174.89 349.779C78.612 349.779 0 271.462 0 174.89S78.612 0 174.89 0c23.26 0 45.931 4.417 67.129 13.543 5.889 2.65 8.833 9.422 6.478 15.605-2.649 5.888-9.421 8.833-15.604 6.477-18.549-7.655-37.98-11.482-58.002-11.482-83.323 0-151.041 67.718-151.041 151.041s67.717 151.041 151.04 151.041 151.041-67.718 151.041-151.041c0-17.96-2.944-35.332-9.127-51.819-2.355-6.183.883-12.955 7.066-15.31s12.954.883 15.31 7.066c7.066 19.138 10.6 39.453 10.6 60.063-.001 95.983-78.318 174.595-174.89 174.595"
5
- />
6
- <path
7
- fill="#FDB933"
8
- d="m251.44 156.93-27.086 28.264 15.015 63.302h34.153zm-11.187-83.618 9.421 9.422-74.784 79.201-63.891-65.658-36.803 152.219h34.154l20.315-84.5 46.225 50.347 98.927-107.76 9.422 9.716 9.421-53.292z"
9
- />
1
+ <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="349.779" height="349.779">
2
+ <path
3
+ fill="#FFF"
4
+ d="M174.89 349.779C78.612 349.779 0 271.462 0 174.89S78.612 0 174.89 0c23.26 0 45.931 4.417 67.129 13.543 5.889 2.65 8.833 9.422 6.478 15.605-2.649 5.888-9.421 8.833-15.604 6.477-18.549-7.655-37.98-11.482-58.002-11.482-83.323 0-151.041 67.718-151.041 151.041s67.717 151.041 151.04 151.041 151.041-67.718 151.041-151.041c0-17.96-2.944-35.332-9.127-51.819-2.355-6.183.883-12.955 7.066-15.31s12.954.883 15.31 7.066c7.066 19.138 10.6 39.453 10.6 60.063-.001 95.983-78.318 174.595-174.89 174.595"
5
+ />
6
+ <path
7
+ fill="#FDB933"
8
+ d="m251.44 156.93-27.086 28.264 15.015 63.302h34.153zm-11.187-83.618 9.421 9.422-74.784 79.201-63.891-65.658-36.803 152.219h34.154l20.315-84.5 46.225 50.347 98.927-107.76 9.422 9.716 9.421-53.292z"
9
+ />
10
10
  </svg>
@@ -162,7 +162,26 @@ exports.segmentFields = [
162
162
  {
163
163
  displayName: 'Type',
164
164
  name: 'type',
165
- type: 'string',
165
+ type: 'options',
166
+ options: [
167
+ { name: 'Boolean', value: 'boolean' },
168
+ { name: 'Date', value: 'date' },
169
+ { name: 'DateTime', value: 'datetime' },
170
+ { name: 'Email', value: 'email' },
171
+ { name: 'Country', value: 'country' },
172
+ { name: 'Locale', value: 'locale' },
173
+ { name: 'Lookup', value: 'lookup' },
174
+ { name: 'Number', value: 'number' },
175
+ { name: 'Tel', value: 'tel' },
176
+ { name: 'Region', value: 'region' },
177
+ { name: 'Select', value: 'select' },
178
+ { name: 'Multiselect', value: 'multiselect' },
179
+ { name: 'Text', value: 'text' },
180
+ { name: 'Textarea', value: 'textarea' },
181
+ { name: 'Time', value: 'time' },
182
+ { name: 'Timezone', value: 'timezone' },
183
+ { name: 'URL', value: 'url' },
184
+ ],
166
185
  default: 'email',
167
186
  },
168
187
  {
@@ -178,6 +197,10 @@ exports.segmentFields = [
178
197
  options: [
179
198
  { name: '=', value: '=' },
180
199
  { name: '!=', value: '!=' },
200
+ { name: '>', value: 'gt' },
201
+ { name: '>=', value: 'gte' },
202
+ { name: '<', value: 'lt' },
203
+ { name: '<=', value: 'lte' },
181
204
  { name: 'Empty', value: 'empty' },
182
205
  { name: 'Not Empty', value: '!empty' },
183
206
  { name: 'Like', value: 'like' },
@@ -187,9 +210,20 @@ exports.segmentFields = [
187
210
  { name: 'Starts With', value: 'startsWith' },
188
211
  { name: 'Ends With', value: 'endsWith' },
189
212
  { name: 'Contains', value: 'contains' },
213
+ { name: 'In', value: 'in' },
214
+ { name: 'Not In', value: '!in' },
215
+ { name: 'Between', value: 'between' },
216
+ { name: 'Not Between', value: '!between' },
190
217
  ],
191
218
  default: 'like',
192
219
  },
220
+ {
221
+ displayName: 'Display',
222
+ name: 'display',
223
+ type: 'string',
224
+ default: '',
225
+ description: 'Optional display name for the filter',
226
+ },
193
227
  ],
194
228
  },
195
229
  ],
@@ -48,9 +48,11 @@ async function createCampaign(context, itemIndex) {
48
48
  }
49
49
  async function updateCampaign(context, itemIndex) {
50
50
  const campaignId = (0, ApiHelpers_1.getRequiredParam)(context, 'campaignId', itemIndex);
51
+ const createIfNotFound = (0, ApiHelpers_1.getOptionalParam)(context, 'createIfNotFound', itemIndex, false);
51
52
  const updateFields = (0, ApiHelpers_1.getOptionalParam)(context, 'updateFields', itemIndex, {});
52
53
  const body = { ...updateFields };
53
- const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'PATCH', `/campaigns/${campaignId}/edit`, body);
54
+ const method = createIfNotFound ? 'PUT' : 'PATCH';
55
+ const response = await (0, ApiHelpers_1.makeApiRequest)(context, method, `/campaigns/${campaignId}/edit`, body);
54
56
  return response.campaign;
55
57
  }
56
58
  async function cloneCampaign(context, itemIndex) {
@@ -61,7 +63,7 @@ async function cloneCampaign(context, itemIndex) {
61
63
  async function getCampaign(context, itemIndex) {
62
64
  const campaignId = (0, ApiHelpers_1.getRequiredParam)(context, 'campaignId', itemIndex);
63
65
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', `/campaigns/${campaignId}`);
64
- return response.campaign;
66
+ return (0, DataHelpers_1.convertNumericStrings)(response.campaign);
65
67
  }
66
68
  async function getAllCampaigns(context, itemIndex) {
67
69
  const returnAll = (0, ApiHelpers_1.getOptionalParam)(context, 'returnAll', itemIndex, false);
@@ -72,12 +74,13 @@ async function getAllCampaigns(context, itemIndex) {
72
74
  if (!qs.orderByDir)
73
75
  qs.orderByDir = 'asc';
74
76
  if (returnAll) {
75
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'campaigns', 'GET', '/campaigns', {}, qs);
77
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'campaigns', 'GET', '/campaigns', {}, qs);
78
+ return (0, DataHelpers_1.convertNumericStrings)(result);
76
79
  }
77
80
  else {
78
81
  qs.limit = (0, ApiHelpers_1.getOptionalParam)(context, 'limit', itemIndex, 30);
79
82
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', '/campaigns', {}, qs);
80
- return response.campaigns;
83
+ return (0, DataHelpers_1.convertNumericStrings)(response.campaigns);
81
84
  }
82
85
  }
83
86
  async function getCampaignContacts(context, itemIndex) {
@@ -86,12 +89,13 @@ async function getCampaignContacts(context, itemIndex) {
86
89
  const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {});
87
90
  const qs = (0, DataHelpers_1.buildQueryFromOptions)(options);
88
91
  if (returnAll) {
89
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'contacts', 'GET', `/campaigns/${campaignId}/contacts`, {}, qs);
92
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'contacts', 'GET', `/campaigns/${campaignId}/contacts`, {}, qs);
93
+ return (0, DataHelpers_1.convertNumericStrings)(result);
90
94
  }
91
95
  else {
92
96
  qs.limit = (0, ApiHelpers_1.getOptionalParam)(context, 'limit', itemIndex, 30);
93
97
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', `/campaigns/${campaignId}/contacts`, {}, qs);
94
- return response.contacts;
98
+ return (0, DataHelpers_1.convertNumericStrings)(response.contacts);
95
99
  }
96
100
  }
97
101
  async function deleteCampaign(context, itemIndex) {
@@ -131,7 +131,7 @@ async function getCompany(context, itemIndex) {
131
131
  if (simple) {
132
132
  result = result.fields.all;
133
133
  }
134
- return result;
134
+ return (0, DataHelpers_1.convertNumericStrings)(result);
135
135
  }
136
136
  async function getAllCompanies(context, itemIndex) {
137
137
  const returnAll = (0, ApiHelpers_1.getOptionalParam)(context, 'returnAll', itemIndex, false);
@@ -156,7 +156,7 @@ async function getAllCompanies(context, itemIndex) {
156
156
  if (simple) {
157
157
  responseData = responseData.map((item) => item.fields.all);
158
158
  }
159
- return responseData;
159
+ return (0, DataHelpers_1.convertNumericStrings)(responseData);
160
160
  }
161
161
  async function deleteCompany(context, itemIndex) {
162
162
  const simple = (0, ApiHelpers_1.getOptionalParam)(context, 'simple', itemIndex, false);
@@ -72,6 +72,12 @@ async function executeContactOperation(context, operation, i) {
72
72
  case 'getAllActivity':
73
73
  responseData = await getAllContactActivity(context, i);
74
74
  break;
75
+ case 'getOwners':
76
+ responseData = await getContactOwners(context);
77
+ break;
78
+ case 'getFields':
79
+ responseData = await getContactFields(context);
80
+ break;
75
81
  default:
76
82
  throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Operation '${operation}' is not supported for Contact resource.`, { itemIndex: i });
77
83
  }
@@ -152,7 +158,8 @@ async function getContact(context, itemIndex) {
152
158
  const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
153
159
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', `/contacts/${contactId}`);
154
160
  const contactData = [response.contact];
155
- return (0, DataHelpers_1.processContactFields)(contactData, options, options.fieldsToReturn);
161
+ const processedData = (0, DataHelpers_1.processContactFields)(contactData, options, options.fieldsToReturn);
162
+ return (0, DataHelpers_1.convertNumericStrings)(processedData);
156
163
  }
157
164
  async function getAllContacts(context, itemIndex) {
158
165
  const returnAll = (0, ApiHelpers_1.getOptionalParam)(context, 'returnAll', itemIndex, false);
@@ -190,7 +197,8 @@ async function getAllContacts(context, itemIndex) {
190
197
  responseData = response.contacts ? Object.values(response.contacts) : [];
191
198
  }
192
199
  }
193
- return (0, DataHelpers_1.processContactFields)(responseData, options, options.fieldsToReturn);
200
+ const processedData = (0, DataHelpers_1.processContactFields)(responseData, options, options.fieldsToReturn);
201
+ return (0, DataHelpers_1.convertNumericStrings)(processedData);
194
202
  }
195
203
  async function deleteContact(context, itemIndex) {
196
204
  const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {});
@@ -220,8 +228,16 @@ async function editContactPoints(context, itemIndex) {
220
228
  const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
221
229
  const action = (0, ApiHelpers_1.getRequiredParam)(context, 'action', itemIndex);
222
230
  const points = (0, ApiHelpers_1.getRequiredParam)(context, 'points', itemIndex);
223
- const body = { points };
224
- const endpoint = action === 'add' ? `/contacts/${contactId}/points/plus` : `/contacts/${contactId}/points/minus`;
231
+ const eventName = (0, ApiHelpers_1.getOptionalParam)(context, 'eventName', itemIndex, '');
232
+ const actionName = (0, ApiHelpers_1.getOptionalParam)(context, 'actionName', itemIndex, '');
233
+ const body = {};
234
+ if (eventName)
235
+ body.eventName = eventName;
236
+ if (actionName)
237
+ body.actionName = actionName;
238
+ const endpoint = action === 'add'
239
+ ? `/contacts/${contactId}/points/plus/${points}`
240
+ : `/contacts/${contactId}/points/minus/${points}`;
225
241
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'POST', endpoint, body);
226
242
  return response.contact;
227
243
  }
@@ -229,8 +245,20 @@ async function editDoNotContactList(context, itemIndex) {
229
245
  const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
230
246
  const action = (0, ApiHelpers_1.getRequiredParam)(context, 'action', itemIndex);
231
247
  const channel = (0, ApiHelpers_1.getRequiredParam)(context, 'channel', itemIndex);
232
- const body = { action, channel };
233
- const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'POST', `/contacts/${contactId}/dnc/${channel}/${action}`, body);
248
+ const reason = (0, ApiHelpers_1.getOptionalParam)(context, 'reason', itemIndex, 3); // Default to Manual (3)
249
+ const channelId = (0, ApiHelpers_1.getOptionalParam)(context, 'channelId', itemIndex, '');
250
+ const comments = (0, ApiHelpers_1.getOptionalParam)(context, 'comments', itemIndex, '');
251
+ const body = {};
252
+ if (reason !== undefined)
253
+ body.reason = reason;
254
+ if (channelId)
255
+ body.channelId = channelId;
256
+ if (comments)
257
+ body.comments = comments;
258
+ const endpoint = action === 'add'
259
+ ? `/contacts/${contactId}/dnc/${channel}/add`
260
+ : `/contacts/${contactId}/dnc/${channel}/remove`;
261
+ const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'POST', endpoint, body);
234
262
  return response.contact;
235
263
  }
236
264
  async function addUtmTags(context, itemIndex) {
@@ -270,7 +298,8 @@ async function removeUtmTags(context, itemIndex) {
270
298
  }
271
299
  async function getContactDevices(context, itemIndex) {
272
300
  const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
273
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'devices', 'GET', `/contacts/${contactId}/devices`);
301
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'devices', 'GET', `/contacts/${contactId}/devices`);
302
+ return (0, DataHelpers_1.convertNumericStrings)(result);
274
303
  }
275
304
  async function getContactActivity(context, itemIndex) {
276
305
  const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
@@ -292,25 +321,30 @@ async function getContactActivity(context, itemIndex) {
292
321
  qs.order = [options.orderBy, options.orderByDir ?? 'asc'];
293
322
  if (options.limit)
294
323
  qs.limit = options.limit;
295
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'events', 'GET', `/contacts/${contactId}/activity`, {}, qs);
324
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'events', 'GET', `/contacts/${contactId}/activity`, {}, qs);
325
+ return (0, DataHelpers_1.convertNumericStrings)(result);
296
326
  }
297
327
  async function getContactNotes(context, itemIndex) {
298
328
  const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
299
329
  const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {});
300
330
  const qs = options;
301
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'notes', 'GET', `/contacts/${contactId}/notes`, {}, qs);
331
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'notes', 'GET', `/contacts/${contactId}/notes`, {}, qs);
332
+ return (0, DataHelpers_1.convertNumericStrings)(result);
302
333
  }
303
334
  async function getContactCompanies(context, itemIndex) {
304
335
  const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
305
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'companies', 'GET', `/contacts/${contactId}/companies`);
336
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'companies', 'GET', `/contacts/${contactId}/companies`);
337
+ return (0, DataHelpers_1.convertNumericStrings)(result);
306
338
  }
307
339
  async function getContactCampaigns(context, itemIndex) {
308
340
  const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
309
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'campaigns', 'GET', `/contacts/${contactId}/campaigns`);
341
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'campaigns', 'GET', `/contacts/${contactId}/campaigns`);
342
+ return (0, DataHelpers_1.convertNumericStrings)(result);
310
343
  }
311
344
  async function getContactSegments(context, itemIndex) {
312
345
  const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
313
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'segments', 'GET', `/contacts/${contactId}/segments`);
346
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'segments', 'GET', `/contacts/${contactId}/segments`);
347
+ return (0, DataHelpers_1.convertNumericStrings)(result);
314
348
  }
315
349
  async function addContactToSegments(context, itemIndex) {
316
350
  const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
@@ -359,7 +393,8 @@ async function getAllContactActivity(context, itemIndex) {
359
393
  qs.order = [options.orderBy, options.orderByDir ?? 'asc'];
360
394
  if (options.limit)
361
395
  qs.limit = options.limit;
362
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'events', 'GET', `/contacts/activity`, {}, qs);
396
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'events', 'GET', `/contacts/activity`, {}, qs);
397
+ return (0, DataHelpers_1.convertNumericStrings)(result);
363
398
  }
364
399
  function normalizeTagsInput(tagsInput) {
365
400
  // Handle different input formats for tags
@@ -478,3 +513,11 @@ async function getContactsWithDncFilter(context, qs, options, limit) {
478
513
  }
479
514
  return contacts;
480
515
  }
516
+ async function getContactOwners(context) {
517
+ const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', '/contacts/list/owners');
518
+ return (0, DataHelpers_1.convertNumericStrings)(response);
519
+ }
520
+ async function getContactFields(context) {
521
+ const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', '/contacts/list/fields');
522
+ return (0, DataHelpers_1.convertNumericStrings)(response);
523
+ }
@@ -142,7 +142,7 @@ async function getField(context, itemIndex) {
142
142
  const fieldId = (0, ApiHelpers_1.getRequiredParam)(context, 'fieldId', itemIndex);
143
143
  const endpoint = `/fields/${fieldObject}/${fieldId}`;
144
144
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', endpoint);
145
- return response.field;
145
+ return (0, DataHelpers_1.convertNumericStrings)(response.field);
146
146
  }
147
147
  async function getAllFields(context, itemIndex) {
148
148
  const fieldObject = (0, ApiHelpers_1.getRequiredParam)(context, 'fieldObject', itemIndex);
@@ -151,12 +151,14 @@ async function getAllFields(context, itemIndex) {
151
151
  const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {});
152
152
  const query = (0, DataHelpers_1.buildQueryFromOptions)(options);
153
153
  if (returnAll) {
154
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'fields', 'GET', `/fields/${fieldObject}`, {}, query);
154
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'fields', 'GET', `/fields/${fieldObject}`, {}, query);
155
+ return (0, DataHelpers_1.convertNumericStrings)(result);
155
156
  }
156
157
  else {
157
158
  query.limit = limit;
158
159
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', `/fields/${fieldObject}`, {}, query);
159
- return Object.values(response.fields || {});
160
+ const data = Object.values(response.fields || {});
161
+ return (0, DataHelpers_1.convertNumericStrings)(data);
160
162
  }
161
163
  }
162
164
  async function deleteField(context, itemIndex) {
@@ -161,7 +161,7 @@ async function updateTag(context, itemIndex) {
161
161
  async function getTag(context, itemIndex) {
162
162
  const tagId = (0, ApiHelpers_1.getRequiredParam)(context, 'tagId', itemIndex);
163
163
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', `/tags/${tagId}`);
164
- return response.tag;
164
+ return (0, DataHelpers_1.convertNumericStrings)(response.tag);
165
165
  }
166
166
  async function getAllTags(context, itemIndex) {
167
167
  const returnAll = (0, ApiHelpers_1.getOptionalParam)(context, 'returnAll', itemIndex, false);
@@ -173,12 +173,14 @@ async function getAllTags(context, itemIndex) {
173
173
  qs.orderByDir = 'asc';
174
174
  if (returnAll) {
175
175
  const limit = (0, ApiHelpers_1.getOptionalParam)(context, 'limit', itemIndex, undefined);
176
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'tags', 'GET', '/tags', {}, qs, limit);
176
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'tags', 'GET', '/tags', {}, qs, limit);
177
+ return (0, DataHelpers_1.convertNumericStrings)(result);
177
178
  }
178
179
  else {
179
180
  qs.limit = (0, ApiHelpers_1.getOptionalParam)(context, 'limit', itemIndex, 30);
180
181
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', '/tags', {}, qs);
181
- return response.tags ? Object.values(response.tags) : [];
182
+ const data = response.tags ? Object.values(response.tags) : [];
183
+ return (0, DataHelpers_1.convertNumericStrings)(data);
182
184
  }
183
185
  }
184
186
  async function deleteTag(context, itemIndex) {
@@ -203,6 +205,7 @@ async function createCategory(context, itemIndex) {
203
205
  }
204
206
  async function updateCategory(context, itemIndex) {
205
207
  const categoryId = (0, ApiHelpers_1.getRequiredParam)(context, 'categoryId', itemIndex);
208
+ const createIfNotFound = (0, ApiHelpers_1.getOptionalParam)(context, 'createIfNotFound', itemIndex, false);
206
209
  const updateFields = (0, ApiHelpers_1.getOptionalParam)(context, 'updateFields', itemIndex, {});
207
210
  const body = {};
208
211
  if (updateFields.title)
@@ -211,13 +214,16 @@ async function updateCategory(context, itemIndex) {
211
214
  body.description = updateFields.description;
212
215
  if (updateFields.color)
213
216
  body.color = updateFields.color;
214
- const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'PATCH', `/categories/${categoryId}/edit`, body);
217
+ if (updateFields.bundle)
218
+ body.bundle = updateFields.bundle;
219
+ const method = createIfNotFound ? 'PUT' : 'PATCH';
220
+ const response = await (0, ApiHelpers_1.makeApiRequest)(context, method, `/categories/${categoryId}/edit`, body);
215
221
  return response.category;
216
222
  }
217
223
  async function getCategory(context, itemIndex) {
218
224
  const categoryId = (0, ApiHelpers_1.getRequiredParam)(context, 'categoryId', itemIndex);
219
225
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', `/categories/${categoryId}`);
220
- return response.category;
226
+ return (0, DataHelpers_1.convertNumericStrings)(response.category);
221
227
  }
222
228
  async function getAllCategories(context, itemIndex) {
223
229
  const returnAll = (0, ApiHelpers_1.getOptionalParam)(context, 'returnAll', itemIndex, false);
@@ -231,7 +237,8 @@ async function getAllCategories(context, itemIndex) {
231
237
  if (!returnAll) {
232
238
  limit = (0, ApiHelpers_1.getOptionalParam)(context, 'limit', itemIndex, 30);
233
239
  }
234
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'categories', 'GET', '/categories', {}, qs, limit);
240
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'categories', 'GET', '/categories', {}, qs, limit);
241
+ return (0, DataHelpers_1.convertNumericStrings)(result);
235
242
  }
236
243
  async function deleteCategory(context, itemIndex) {
237
244
  const categoryId = (0, ApiHelpers_1.getRequiredParam)(context, 'categoryId', itemIndex);
@@ -111,7 +111,7 @@ async function getNotification(context, itemIndex) {
111
111
  const notificationId = (0, ApiHelpers_1.getRequiredParam)(context, 'notificationId', itemIndex);
112
112
  const endpoint = `/notifications/${notificationId}`;
113
113
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', endpoint);
114
- return response.notification;
114
+ return (0, DataHelpers_1.convertNumericStrings)(response.notification);
115
115
  }
116
116
  async function getAllNotifications(context, itemIndex) {
117
117
  const returnAll = (0, ApiHelpers_1.getOptionalParam)(context, 'returnAll', itemIndex, false);
@@ -119,12 +119,14 @@ async function getAllNotifications(context, itemIndex) {
119
119
  const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {});
120
120
  const query = (0, DataHelpers_1.buildQueryFromOptions)(options);
121
121
  if (returnAll) {
122
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'notifications', 'GET', '/notifications', {}, query);
122
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'notifications', 'GET', '/notifications', {}, query);
123
+ return (0, DataHelpers_1.convertNumericStrings)(result);
123
124
  }
124
125
  else {
125
126
  query.limit = limit;
126
127
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', '/notifications', {}, query);
127
- return Object.values(response.notifications || {});
128
+ const data = Object.values(response.notifications || {});
129
+ return (0, DataHelpers_1.convertNumericStrings)(data);
128
130
  }
129
131
  }
130
132
  async function deleteNotification(context, itemIndex) {
@@ -62,20 +62,22 @@ async function updateSegment(context, itemIndex) {
62
62
  async function getSegment(context, itemIndex) {
63
63
  const segmentId = (0, ApiHelpers_1.getRequiredParam)(context, 'segmentId', itemIndex);
64
64
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', `/segments/${segmentId}`);
65
- return response.list;
65
+ return (0, DataHelpers_1.convertNumericStrings)(response.list);
66
66
  }
67
67
  async function getAllSegments(context, itemIndex) {
68
68
  const returnAll = (0, ApiHelpers_1.getOptionalParam)(context, 'returnAll', itemIndex, false);
69
69
  const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {});
70
70
  const qs = (0, DataHelpers_1.buildQueryFromOptions)(options);
71
71
  if (returnAll) {
72
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'lists', 'GET', '/segments', {}, qs);
72
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'lists', 'GET', '/segments', {}, qs);
73
+ return (0, DataHelpers_1.convertNumericStrings)(result);
73
74
  }
74
75
  else {
75
76
  const limit = (0, ApiHelpers_1.getOptionalParam)(context, 'limit', itemIndex, 30);
76
77
  qs.limit = limit;
77
78
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', '/segments', {}, qs);
78
- return response.lists ? Object.values(response.lists) : [];
79
+ const data = response.lists ? Object.values(response.lists) : [];
80
+ return (0, DataHelpers_1.convertNumericStrings)(data);
79
81
  }
80
82
  }
81
83
  async function deleteSegment(context, itemIndex) {
@@ -58,7 +58,9 @@ function handleApiError(context, error, operation, resource) {
58
58
  }
59
59
  }
60
60
  // Check for errors array (current Mautic API format)
61
- else if (errorData?.errors && Array.isArray(errorData.errors) && errorData.errors.length > 0) {
61
+ else if (errorData?.errors &&
62
+ Array.isArray(errorData.errors) &&
63
+ errorData.errors.length > 0) {
62
64
  const validationErrors = [];
63
65
  errorData.errors.forEach((err) => {
64
66
  if (err.details && typeof err.details === 'object') {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createBatchSuccessResponse = exports.processBatchIds = exports.buildQueryFromOptions = exports.validateJsonParameter = exports.processContactFields = exports.wrapSingleItem = exports.processSimpleResponse = void 0;
3
+ exports.convertNumericStrings = exports.createBatchSuccessResponse = exports.processBatchIds = exports.buildQueryFromOptions = exports.validateJsonParameter = exports.processContactFields = exports.wrapSingleItem = exports.processSimpleResponse = void 0;
4
4
  const GenericFunctions_1 = require("../GenericFunctions");
5
5
  // Process simple response data extraction
6
6
  function processSimpleResponse(responseData, simple, dataPath) {
@@ -102,3 +102,33 @@ function createBatchSuccessResponse(operation, ids, customMessage) {
102
102
  };
103
103
  }
104
104
  exports.createBatchSuccessResponse = createBatchSuccessResponse;
105
+ // Convert numeric strings to numbers recursively
106
+ function convertNumericStrings(data) {
107
+ if (data === null || data === undefined) {
108
+ return data;
109
+ }
110
+ if (typeof data === 'string') {
111
+ // Check if string is a valid number (including negative numbers and decimals)
112
+ const numericRegex = /^-?\d+(\.\d+)?$/;
113
+ if (numericRegex.test(data)) {
114
+ const num = parseFloat(data);
115
+ // Only convert if parseFloat doesn't lose precision and result is finite
116
+ if (!isNaN(num) && isFinite(num) && num.toString() === data) {
117
+ return num;
118
+ }
119
+ }
120
+ return data;
121
+ }
122
+ if (Array.isArray(data)) {
123
+ return data.map(convertNumericStrings);
124
+ }
125
+ if (typeof data === 'object') {
126
+ const converted = {};
127
+ for (const [key, value] of Object.entries(data)) {
128
+ converted[key] = convertNumericStrings(value);
129
+ }
130
+ return converted;
131
+ }
132
+ return data;
133
+ }
134
+ exports.convertNumericStrings = convertNumericStrings;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-mautic-advanced",
3
- "version": "0.3.8",
3
+ "version": "0.4.0",
4
4
  "description": "Enhanced n8n node for Mautic with comprehensive API coverage including tags, campaigns, categories, and advanced contact management",
5
5
  "keywords": [
6
6
  "n8n",