@unstable-dev/unmeshed-mcp 0.1.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.
Files changed (47) hide show
  1. package/README.md +64 -0
  2. package/dist/auth.d.ts +6 -0
  3. package/dist/auth.js +11 -0
  4. package/dist/client.d.ts +46 -0
  5. package/dist/client.js +97 -0
  6. package/dist/config.d.ts +10 -0
  7. package/dist/config.js +31 -0
  8. package/dist/get-docs.d.ts +8 -0
  9. package/dist/get-docs.js +64 -0
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.js +35 -0
  12. package/dist/server.d.ts +4 -0
  13. package/dist/server.js +203 -0
  14. package/knowledge/README.md +16 -0
  15. package/knowledge/SKILL.md +359 -0
  16. package/knowledge/assets/patterns.md +637 -0
  17. package/knowledge/execution/debugging-guide.md +18 -0
  18. package/knowledge/execution/process-run.schema.md +24 -0
  19. package/knowledge/execution/step-run.schema.md +21 -0
  20. package/knowledge/process-definition.schema.md +36 -0
  21. package/knowledge/references/integrations.md +914 -0
  22. package/knowledge/references/steps-knowledge.md +834 -0
  23. package/knowledge/step-definition.schema.md +140 -0
  24. package/knowledge/step-output-paths.md +45 -0
  25. package/knowledge/steps/DECISION_ENGINE.md +248 -0
  26. package/knowledge/steps/DEPENDSON.md +296 -0
  27. package/knowledge/steps/EXIT.md +220 -0
  28. package/knowledge/steps/FAIL.md +198 -0
  29. package/knowledge/steps/FLOW_GATEWAY.md +405 -0
  30. package/knowledge/steps/FOREACH.md +250 -0
  31. package/knowledge/steps/HTTP.md +183 -0
  32. package/knowledge/steps/JAVASCRIPT.md +192 -0
  33. package/knowledge/steps/JQ.md +189 -0
  34. package/knowledge/steps/LIST.md +279 -0
  35. package/knowledge/steps/NOOP.md +165 -0
  36. package/knowledge/steps/PARALLEL.md +366 -0
  37. package/knowledge/steps/PYTHON.md +206 -0
  38. package/knowledge/steps/SEND_RESPONSE.md +301 -0
  39. package/knowledge/steps/SQLITE.md +301 -0
  40. package/knowledge/steps/SUB_PROCESS.md +296 -0
  41. package/knowledge/steps/SWITCH.md +369 -0
  42. package/knowledge/steps/UPDATE_STEP.md +257 -0
  43. package/knowledge/steps/WAIT.md +218 -0
  44. package/knowledge/steps/WHILE.md +328 -0
  45. package/knowledge/steps/WORKER.md +233 -0
  46. package/knowledge/system-prompt.md +274 -0
  47. package/package.json +39 -0
@@ -0,0 +1,914 @@
1
+ # Unmeshed Integrations — Input Schemas
2
+
3
+ All integrations use `"type": "INTEGRATION"` as the step type.
4
+ The `input.type` field selects the specific integration.
5
+
6
+ ---
7
+
8
+ ## LLM — Claude (Anthropic)
9
+
10
+ **`input.type`**: `"llm-claude"`
11
+
12
+ ```json
13
+ {
14
+ "type": "llm-claude",
15
+ "name": "unmeshed",
16
+ "publishProperties": {
17
+ "allowedTokens": 500,
18
+ "temperature": 0.1
19
+ },
20
+ "messageBody": {
21
+ "systemPrompt": "You are a ... Return only valid JSON. Do not include markdown or explanation.",
22
+ "userPrompt": "Your prompt here with {{steps.<ref>.output.result.<field>}} references.\n\nReturn JSON in this exact format:\n{ ... }"
23
+ }
24
+ }
25
+ ```
26
+
27
+ **Rules:**
28
+ - Always instruct the model to return only JSON in the system prompt when structured output is needed
29
+ - `allowedTokens`: max tokens — can be a number `500` or a string `"100"` — both are valid
30
+ - `temperature`: 0.0–0.1 for deterministic/classification tasks; 0.5–0.9 for generative/creative
31
+ - Output accessed via `steps.<ref>.output.results.<field>` (note: `.results` not `.result`)
32
+ - Always defensively access both: `steps.<ref>.output.results || steps.<ref>.output.result || {}`
33
+ - The `name` field references the configured Claude connection name in Unmeshed (typically `"unmeshed"`)
34
+
35
+ ---
36
+
37
+ ## MongoDB
38
+
39
+ **`input.type`**: `"mongodb"`
40
+
41
+ All operations use `publishProperties.collection` and `messageBody.collection` (both required).
42
+ The connection `name` must match the configured MongoDB connection in Unmeshed.
43
+
44
+ ### FIND_ONE
45
+ ```json
46
+ {
47
+ "type": "mongodb",
48
+ "name": "mongo-test",
49
+ "publishProperties": { "collection": "users" },
50
+ "messageBody": {
51
+ "operation": "FIND_ONE",
52
+ "collection": "users",
53
+ "filter": { "email": "user@example.com" }
54
+ }
55
+ }
56
+ ```
57
+ Output: `steps.<ref>.output.results.<field>` (document fields directly on `.results`)
58
+
59
+ ### INSERT_ONE
60
+ ```json
61
+ {
62
+ "messageBody": {
63
+ "operation": "INSERT_ONE",
64
+ "collection": "users",
65
+ "document": {
66
+ "name": "test user",
67
+ "email": "test@example.com",
68
+ "password": "hashed"
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ ### UPDATE_ONE
75
+ ```json
76
+ {
77
+ "messageBody": {
78
+ "operation": "UPDATE_ONE",
79
+ "collection": "users",
80
+ "filter": { "email": "user@example.com" },
81
+ "update": { "$set": { "name": "New Name" } }
82
+ }
83
+ }
84
+ ```
85
+
86
+ ### DELETE_ONE
87
+ ```json
88
+ {
89
+ "messageBody": {
90
+ "operation": "DELETE_ONE",
91
+ "collection": "users",
92
+ "filter": { "email": "user@example.com" }
93
+ }
94
+ }
95
+ ```
96
+
97
+ ### COUNT
98
+ ```json
99
+ {
100
+ "messageBody": {
101
+ "operation": "COUNT",
102
+ "collection": "users"
103
+ }
104
+ }
105
+ ```
106
+ Output: `steps.<ref>.output.results.count`
107
+
108
+ ### CREATE_COLLECTION / DELETE_COLLECTION
109
+ ```json
110
+ { "messageBody": { "operation": "CREATE_COLLECTION", "collection": "my_collection" } }
111
+ { "messageBody": { "operation": "DELETE_COLLECTION", "collection": "my_collection" } }
112
+ ```
113
+ Output: `steps.<ref>.output.results.success` (boolean)
114
+
115
+ ### LIST_INDEXES
116
+ ```json
117
+ { "messageBody": { "operation": "LIST_INDEXES", "collection": "users" } }
118
+ ```
119
+ Output: `steps.<ref>.output.results.result` (array of index objects, each with a `.key` field)
120
+
121
+ ### CREATE_INDEX
122
+ ```json
123
+ {
124
+ "messageBody": {
125
+ "operation": "CREATE_INDEX",
126
+ "collection": "users",
127
+ "indexName": "my_index",
128
+ "index": { "name": 1, "email": -1 }
129
+ }
130
+ }
131
+ ```
132
+ Index direction: `1` = ascending, `-1` = descending
133
+
134
+ ### DROP_INDEX
135
+ ```json
136
+ { "messageBody": { "operation": "DROP_INDEX", "collection": "users", "indexName": "my_index" } }
137
+ ```
138
+
139
+ **MongoDB output access patterns:**
140
+ ```javascript
141
+ steps.<ref>.output.results.<field> // FIND_ONE document fields
142
+ steps.<ref>.output.results.count // COUNT
143
+ steps.<ref>.output.results.success // CREATE/DELETE_COLLECTION
144
+ steps.<ref>.output.results.result // LIST_INDEXES (array)
145
+ steps.<ref>.output.results.result.length // number of indexes
146
+ steps.<ref>.output.results.result[n].key // index key definition
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Redis
152
+
153
+ **`input.type`**: `"redis"`
154
+
155
+ `publishProperties` is always `{}`. All config goes in `messageBody`.
156
+
157
+ ### SET
158
+ ```json
159
+ {
160
+ "type": "redis",
161
+ "name": "redis_integration",
162
+ "publishProperties": {},
163
+ "messageBody": {
164
+ "operation": "SET",
165
+ "key": "user:1",
166
+ "value": "John Doe"
167
+ }
168
+ }
169
+ ```
170
+ `value` can be a string or a JSON object — Redis stores objects as serialized JSON.
171
+
172
+ ### GET
173
+ ```json
174
+ { "messageBody": { "operation": "GET", "key": "user:1" } }
175
+ ```
176
+ Output: `steps.<ref>.output.results.result` (string) or `steps.<ref>.output.results.<field>` (object fields directly if value was an object)
177
+
178
+ ### DEL
179
+ ```json
180
+ { "messageBody": { "operation": "DEL", "keys": ["user:1", "user:2"] } }
181
+ ```
182
+ `keys` is always an array.
183
+
184
+ ### KEYS (pattern match)
185
+ ```json
186
+ { "messageBody": { "operation": "KEYS", "pattern": "user:*" } }
187
+ ```
188
+ Output: `steps.<ref>.output.results.result` (array of matching key strings)
189
+
190
+ ### TYPE
191
+ ```json
192
+ { "messageBody": { "operation": "TYPE", "key": "user:1" } }
193
+ ```
194
+ Output: `steps.<ref>.output.results.result` (string: `"string"`, `"hash"`, `"list"`, `"set"`, `"zset"`)
195
+
196
+ ### RENAME
197
+ ```json
198
+ { "messageBody": { "operation": "RENAME", "oldKey": "user:10", "newKey": "user:profile:10" } }
199
+ ```
200
+
201
+ ### EVAL (Lua scripting)
202
+ ```json
203
+ {
204
+ "messageBody": {
205
+ "operation": "EVAL",
206
+ "script": "return redis.call('SET', KEYS[1], ARGV[1])",
207
+ "keys": ["user:2"],
208
+ "args": ["value"]
209
+ }
210
+ }
211
+ ```
212
+ Output: `steps.<ref>.output.results.result`
213
+
214
+ **Redis output access patterns:**
215
+ ```javascript
216
+ steps.<ref>.output.results.result // string value, array (KEYS), or Lua script result
217
+ steps.<ref>.output.results.<field> // object fields when value was stored as JSON object
218
+ // Example: if SET key="user:10" value={"name":"Alice","age":30}
219
+ // then GET → steps.<ref>.output.results.name === "Alice"
220
+ // steps.<ref>.output.results.age === 30
221
+ ```
222
+
223
+ ---
224
+
225
+ ## Airtable
226
+
227
+ **`input.type`**: `"airtable"`
228
+
229
+ `publishProperties` is always `{}`. All config goes in `messageBody`.
230
+
231
+ ### GET_TABLES
232
+ ```json
233
+ {
234
+ "type": "airtable",
235
+ "name": "airtable-test",
236
+ "publishProperties": {},
237
+ "messageBody": {
238
+ "action": "GET_TABLES"
239
+ }
240
+ }
241
+ ```
242
+ Output: `steps.<ref>.output.tables` (array of table objects with `.name`, `.id`)
243
+ Access first table: `steps.<ref>.output.tables[0].name`
244
+
245
+ ### LIST (read all records)
246
+ ```json
247
+ {
248
+ "messageBody": {
249
+ "action": "LIST",
250
+ "tableName": "{{ steps.get_all_tables.output.tables[0].name }}",
251
+ "pageSize": "100",
252
+ "maxRecords": "100"
253
+ }
254
+ }
255
+ ```
256
+ Output: `steps.<ref>.output.records` (array of record objects)
257
+ Each record: `{ id, fields: { FieldName: value } }`
258
+
259
+ ### CREATE
260
+ ```json
261
+ {
262
+ "messageBody": {
263
+ "action": "CREATE",
264
+ "tableName": "Table 1",
265
+ "records": [
266
+ { "fields": { "Name": "Sample", "Email": "user@example.com" } }
267
+ ]
268
+ }
269
+ }
270
+ ```
271
+ Output: `steps.<ref>.output.records[0].id` — the created record ID
272
+
273
+ ### READ (single record by ID)
274
+ ```json
275
+ {
276
+ "messageBody": {
277
+ "action": "READ",
278
+ "tableName": "Table 1",
279
+ "recordId": "{{ steps.<ref>.output.records[0].id }}"
280
+ }
281
+ }
282
+ ```
283
+ Output: `steps.<ref>.output.fields.<FieldName>`
284
+
285
+ ### UPDATE
286
+ ```json
287
+ {
288
+ "messageBody": {
289
+ "action": "UPDATE",
290
+ "tableName": "Table 1",
291
+ "records": [
292
+ {
293
+ "id": "{{ steps.<ref>.output.records[0].id }}",
294
+ "fields": { "Email": "new@example.com" }
295
+ }
296
+ ]
297
+ }
298
+ }
299
+ ```
300
+
301
+ ### DELETE
302
+ ```json
303
+ {
304
+ "messageBody": {
305
+ "action": "DELETE",
306
+ "tableName": "Table 1",
307
+ "recordIds": ["{{ steps.<ref>.output.records[0].id }}"]
308
+ }
309
+ }
310
+ ```
311
+
312
+ **Airtable rules:**
313
+ - `tableName` can be a static string or a `{{...}}` template (e.g. `steps.get_all_tables.output.tables[0].name`)
314
+ - `pageSize` and `maxRecords` are strings, not numbers: `"100"` not `100`
315
+ - Mark upstream Airtable steps `optional: true` when rate-limiting is possible; use a SWITCH after to check for `HTTP 429` errors
316
+ - Rate-limit check script: `steps.get_all_tables.output.error?.includes("HTTP 429 429 TOO_MANY_REQUESTS")`
317
+
318
+ ---
319
+
320
+ ## Jira
321
+
322
+ **`input.type`**: `"jira"`
323
+
324
+ All operations use `publishProperties` (no `messageBody` payload). The `action` field in `publishProperties` selects the operation.
325
+
326
+ ```json
327
+ {
328
+ "type": "jira",
329
+ "name": "jira-test",
330
+ "publishProperties": {
331
+ "action": "<ACTION>",
332
+ "project_key": "KAN",
333
+ "issue_type": "Task",
334
+ "summary": "Issue summary",
335
+ "description": "Description",
336
+ "priority": "Medium",
337
+ "assignee": "",
338
+ ...
339
+ },
340
+ "messageBody": {}
341
+ }
342
+ ```
343
+
344
+ ### GET_ISSUE
345
+ ```json
346
+ { "action": "GET_ISSUE", "issue_key": "KAN-2", "fields": "*all" }
347
+ ```
348
+ Output: `steps.<ref>.output.status`, `steps.<ref>.output.summary`, `steps.<ref>.output.description`, etc.
349
+
350
+ ### SEARCH_ISSUES
351
+ ```json
352
+ { "action": "SEARCH_ISSUES", "jql": "project = KAN AND issuetype = Bug", "maxResults": "100", "fields": "*all" }
353
+ ```
354
+ Output: `steps.<ref>.output.issues` (array)
355
+ Each issue: `steps.<ref>.output.issues[0].key`, `steps.<ref>.output.issues[0].fields.summary`, `steps.<ref>.output.issues[0].fields.description.content[0].content[0].text`
356
+
357
+ ### CREATE_ISSUE
358
+ ```json
359
+ {
360
+ "action": "CREATE_ISSUE",
361
+ "project_key": "KAN",
362
+ "issue_type": "Task",
363
+ "summary": "Issue summary",
364
+ "description": "Description",
365
+ "priority": "Medium",
366
+ "assignee": "",
367
+ "labels": ["backend", "testing"],
368
+ "components": ["API", "Backend"]
369
+ }
370
+ ```
371
+ Output: `steps.<ref>.output.key` (e.g. `"KAN-5"`)
372
+
373
+ ### DELETE_ISSUE
374
+ ```json
375
+ { "action": "DELETE_ISSUE", "issue_key": "{{ steps.create_issue.output.key }}" }
376
+ ```
377
+ Output: `steps.<ref>.output.message` (e.g. `"Issue deleted successfully"`)
378
+
379
+ ### ADD_COMMENT
380
+ ```json
381
+ { "action": "ADD_COMMENT", "issue_key": "KAN-1", "body": "Comment text" }
382
+ ```
383
+ Output: `steps.<ref>.output.id` (comment ID)
384
+
385
+ ### GET_COMMENTS
386
+ ```json
387
+ { "action": "GET_COMMENTS", "issue_key": "KAN-1" }
388
+ ```
389
+ Output: `steps.<ref>.output.comments` (array)
390
+ Comment text: `steps.<ref>.output.comments[0].body.content[0].content[0].text`
391
+
392
+ ### UPDATE_COMMENT
393
+ ```json
394
+ { "action": "UPDATE_COMMENT", "issue_key": "KAN-1", "comment_id": "{{ steps.add_comment.output.id }}", "body": "Updated text" }
395
+ ```
396
+
397
+ ### DELETE_COMMENT
398
+ ```json
399
+ { "action": "DELETE_COMMENT", "issue_key": "KAN-1", "comment_id": "{{ steps.add_comment.output.id }}" }
400
+ ```
401
+
402
+ ### GET_TRANSITIONS
403
+ ```json
404
+ { "action": "GET_TRANSITIONS", "issue_key": "KAN-1" }
405
+ ```
406
+ Output: `steps.<ref>.output.transitions` (array of transition objects)
407
+
408
+ ### GET_ATTACHMENTS
409
+ ```json
410
+ { "action": "GET_ATTACHMENTS", "issue_key": "KAN-1" }
411
+ ```
412
+ Output: `steps.<ref>.output.attachments` (array), each with `.filename`, `.id`, `.content`
413
+
414
+ ### SEARCH_USERS
415
+ ```json
416
+ { "action": "SEARCH_USERS", "query": ".", "maxResults": "100" }
417
+ ```
418
+ Output: `steps.<ref>.output.users` (array)
419
+
420
+ ### GET_CURRENT_USER
421
+ ```json
422
+ { "action": "GET_CURRENT_USER" }
423
+ ```
424
+ Output: `steps.<ref>.output.emailAddress`, `steps.<ref>.output.displayName`
425
+
426
+ **Jira rules:**
427
+ - `messageBody` is always `{}`
428
+ - `maxResults` is a string: `"100"` not `100`
429
+ - `fields`: `"*all"` returns all fields; omit to get default fields only
430
+ - Comment body uses Atlassian Document Format (ADF) — nested `content[0].content[0].text` to reach plain text
431
+ - Always add a WAIT step after CREATE_ISSUE before attempting DELETE_ISSUE to avoid race conditions
432
+
433
+ ---
434
+
435
+ ## Microsoft Outlook
436
+
437
+ **`input.type`**: `"ms-outlook"`
438
+
439
+ ```json
440
+ {
441
+ "type": "ms-outlook",
442
+ "name": "devs-a-outlook-integration",
443
+ "publishProperties": {
444
+ "mailBox": "user@company.io",
445
+ "action": "<ACTION>",
446
+ "folderId": "<folder-id>",
447
+ "folderName": "Inbox",
448
+ "folderParentId": "<parent-folder-id>",
449
+ "receivedFromMinutesAgo": "60",
450
+ "receivedUntilMinutesAgo": "0",
451
+ "readStatusFilter": "UNREAD",
452
+ "messagesInputType": "STEP_REF",
453
+ "markAs": "READ",
454
+ "fromFilter": "",
455
+ "includeAttachments": true
456
+ },
457
+ "messageBody": {}
458
+ }
459
+ ```
460
+
461
+ ### readEmail
462
+ ```json
463
+ { "action": "readEmail", "mailBox": "user@company.io", "readStatusFilter": "UNREAD", "includeAttachments": true }
464
+ ```
465
+ Output: `steps.<ref>.output.messages` (array of email objects)
466
+ Each message: `.subject`, `.from`, `.body`, `.attachments` (array with `.name`, `.contentBytes`)
467
+ Attachments are saved to `/app/files/_outlook_attachments_/` on the Unmeshed filesystem.
468
+
469
+ ### sendEmail
470
+ ```json
471
+ {
472
+ "action": "sendEmail",
473
+ "mailBox": "DEFAULT",
474
+ "subject": "Email subject",
475
+ "to": "recipient@example.com",
476
+ "body": "<html><p>Email body</p></html>",
477
+ "attachments": "{{ steps.python_1.output.result.attachments }}"
478
+ }
479
+ ```
480
+ - `mailBox: "DEFAULT"` uses the configured mailbox
481
+ - `body` can be HTML string
482
+ - `attachments`: array of objects with `{ name, contentType, type: "CONTENT_BYTES", contentBase64 }` — build this in a preceding PYTHON step
483
+
484
+ ### markReadUnread
485
+ ```json
486
+ {
487
+ "action": "markReadUnread",
488
+ "markAs": "READ",
489
+ "messagesInputType": "STEP_REF",
490
+ "stepRef": "read"
491
+ }
492
+ ```
493
+ - `messagesInputType: "STEP_REF"` — takes messages from a previous readEmail step
494
+ - `stepRef`: the `ref` of the readEmail step to mark
495
+
496
+ **Outlook rules:**
497
+ - `folderId` and `folderParentId` are opaque Outlook folder ID strings (from Outlook API)
498
+ - `receivedFromMinutesAgo` and `receivedUntilMinutesAgo` are strings: `"60"` not `60`
499
+ - `messageBody` is always `{}`
500
+ - Use `errorPolicyName: "retry_ep"` on readEmail steps — email delivery can have slight delays
501
+ - Pattern: SUB_PROCESS (trigger send) → WAIT (allow delivery) → readEmail → assert on messages
502
+
503
+ ---
504
+
505
+ ## Google Sheets
506
+
507
+ **`input.type`**: `"google-sheets"`
508
+
509
+ ### CREATE_SPREADSHEET
510
+ ```json
511
+ {
512
+ "type": "google-sheets",
513
+ "name": "g-sheets",
514
+ "publishProperties": {
515
+ "action": "CREATE_SPREADSHEET",
516
+ "title": "{{ steps.<ref>.output.result.<titleField> }}"
517
+ },
518
+ "messageBody": {}
519
+ }
520
+ ```
521
+ Output: `steps.<ref>.output.spreadsheetId`
522
+
523
+ ### APPEND_ROWS
524
+ ```json
525
+ {
526
+ "publishProperties": {
527
+ "action": "APPEND_ROWS",
528
+ "spreadsheetId": "{{ steps.<ref>.output.spreadsheetId }}",
529
+ "range": "Sheet1!A:Z",
530
+ "values": [["col1_value", "col2_value"]]
531
+ },
532
+ "messageBody": {}
533
+ }
534
+ ```
535
+
536
+ ### GET_ROWS
537
+ ```json
538
+ {
539
+ "publishProperties": { "action": "GET_ROWS", "spreadsheetId": "<id>", "range": "Sheet1!A:Z" },
540
+ "messageBody": {}
541
+ }
542
+ ```
543
+ Output: `steps.<ref>.output.result.rows`
544
+
545
+ **Rules:**
546
+ - `values` is a 2D array — each inner array is one row
547
+ - Common pattern: CREATE → store ID in PERSISTED_STATE → APPEND on subsequent runs
548
+
549
+ ---
550
+
551
+ ## Google Drive
552
+
553
+ **`input.type`**: `"google-drive"`
554
+
555
+ ### UPLOAD_FROM_UNMESHED_FILES
556
+ ```json
557
+ {
558
+ "type": "google-drive",
559
+ "name": "google-drive",
560
+ "publishProperties": {
561
+ "action": "UPLOAD_FROM_UNMESHED_FILES",
562
+ "folderId": "REPLACE_WITH_GOOGLE_DRIVE_FOLDER_ID",
563
+ "fileName": "{{ steps.<ref>.output.result.fileName }}",
564
+ "fileLocation": "{{ steps.<ref>.output.result.location }}",
565
+ "unmeshedFilePath": "{{ steps.<ref>.output.result.location }}/{{ steps.<ref>.output.result.fileName }}",
566
+ "googleDrivePath": "/"
567
+ },
568
+ "messageBody": {}
569
+ }
570
+ ```
571
+ Output: `steps.<ref>.output.id`, `steps.<ref>.output.webViewLink`, `steps.<ref>.output.webContentLink`
572
+
573
+ ---
574
+
575
+ ## Gmail
576
+
577
+ Gmail has **two separate integration types** depending on whether you only need to send, or also need to read:
578
+
579
+ | `input.type` | `name` | Use when |
580
+ |---|---|---|
581
+ | `"google-gmail-send"` | your send-only connection | sending email only |
582
+ | `"google-gmail-read-send"` | your read+send connection | sending, reading, or marking email |
583
+
584
+ ---
585
+
586
+ ### Send email (send-only connection)
587
+
588
+ **`input.type`**: `"google-gmail-send"`
589
+
590
+ ```json
591
+ {
592
+ "type": "google-gmail-send",
593
+ "name": "unmeshed-mail",
594
+ "publishProperties": {
595
+ "action": "sendEmail",
596
+ "subject": "Your subject here",
597
+ "to": "{{ steps.<ref>.output.result.email }}",
598
+ "cc": "",
599
+ "bcc": "",
600
+ "body": "Email body text or HTML",
601
+ "bodyContentType": "plain",
602
+ "from": "sender@yourdomain.com"
603
+ },
604
+ "messageBody": {}
605
+ }
606
+ ```
607
+
608
+ ---
609
+
610
+ ### Send email (read+send connection)
611
+
612
+ **`input.type`**: `"google-gmail-read-send"`
613
+
614
+ ```json
615
+ {
616
+ "type": "google-gmail-read-send",
617
+ "name": "gmail-read-send",
618
+ "publishProperties": {
619
+ "action": "sendEmail",
620
+ "subject": "Your subject here",
621
+ "to": "{{ steps.<ref>.output.result.email }}",
622
+ "cc": "",
623
+ "bcc": "",
624
+ "body": "Email body text or HTML",
625
+ "bodyContentType": "plain"
626
+ },
627
+ "messageBody": {}
628
+ }
629
+ ```
630
+
631
+ ---
632
+
633
+ ### Read email
634
+
635
+ **`input.type`**: `"google-gmail-read-send"`
636
+
637
+ ```json
638
+ {
639
+ "type": "google-gmail-read-send",
640
+ "name": "unmeshed-mail",
641
+ "publishProperties": {
642
+ "action": "readEmail",
643
+ "userId": "",
644
+ "labelId": "INBOX",
645
+ "receivedFromMinutesAgo": "30",
646
+ "receivedUntilMinutesAgo": "0",
647
+ "fromFilter": "",
648
+ "subjectFilter": "",
649
+ "readStatusFilter": "ALL",
650
+ "includeAttachments": false,
651
+ "maxCount": 10
652
+ },
653
+ "messageBody": {}
654
+ }
655
+ ```
656
+
657
+ `readStatusFilter` options: `"ALL"`, `"READ"`, `"UNREAD"`
658
+
659
+ ---
660
+
661
+ ### Mark read / unread
662
+
663
+ **`input.type`**: `"google-gmail-read-send"`
664
+
665
+ ```json
666
+ {
667
+ "type": "google-gmail-read-send",
668
+ "name": "gmail-read-send",
669
+ "publishProperties": {
670
+ "action": "markReadUnread",
671
+ "messagesInputType": "STEP_REF",
672
+ "messageIds": "",
673
+ "stepRef": "<ref-of-readEmail-step>",
674
+ "markAs": "READ"
675
+ },
676
+ "messageBody": {}
677
+ }
678
+ ```
679
+
680
+ `messagesInputType`: `"STEP_REF"` to take messages from a prior readEmail step; `stepRef` is that step's `ref`.
681
+ `markAs`: `"READ"` or `"UNREAD"`
682
+
683
+ ---
684
+
685
+ **Gmail rules:**
686
+ - `messageBody` is always `{}`
687
+ - `action` is camelCase: `"sendEmail"`, `"readEmail"`, `"markReadUnread"` — not uppercase like other integrations
688
+ - `bodyContentType`: `"plain"` for plain text, `"html"` for HTML body
689
+ - `from` is only on the `google-gmail-send` type — omit it on `google-gmail-read-send`
690
+ - `receivedFromMinutesAgo` and `receivedUntilMinutesAgo` are strings: `"30"` not `30`
691
+ - `maxCount` is a number: `10` not `"10"`
692
+ - The `name` field must match the connection configured in Unmeshed — check with your team which connection name to use
693
+
694
+ ---
695
+
696
+ ## Slack
697
+
698
+ **`input.type`**: `"slack-messaging"`
699
+
700
+ ```json
701
+ {
702
+ "type": "slack-messaging",
703
+ "name": "",
704
+ "publishProperties": { "durationType": "MINUTES", "receiverType": "CHANNEL" },
705
+ "messageBody": {
706
+ "type": "MESSAGE",
707
+ "message": "{{ steps.<ref>.output.result.slackMessage }}",
708
+ "messageBlocks": [
709
+ { "type": "section", "text": { "type": "mrkdwn", "text": "{{ steps.<ref>.output.result.slackMessage }}" } }
710
+ ]
711
+ }
712
+ }
713
+ ```
714
+
715
+ **Rules:**
716
+ - `input.type` is `"slack-messaging"` not `"slack"`
717
+ - Build the full message string in a preceding JAVASCRIPT step
718
+ - `receiverType`: `"CHANNEL"` or `"USER"`
719
+
720
+ ---
721
+
722
+ ## Notion
723
+
724
+ **`input.type`**: `"notion"`
725
+
726
+ All operations go in `messageBody.operation`. `publishProperties` is `{}` or omitted.
727
+
728
+ ### CREATE_DATABASE
729
+ ```json
730
+ {
731
+ "messageBody": {
732
+ "operation": "CREATE_DATABASE",
733
+ "parent": { "page_id": "<page-id>" },
734
+ "title": [{ "type": "text", "text": { "content": "DB Title" } }],
735
+ "properties": {
736
+ "Name": { "title": {} },
737
+ "Status": { "select": { "options": [{ "name": "Active", "color": "green" }] } },
738
+ "Due Date": { "date": {} },
739
+ "Tags": { "multi_select": { "options": [{ "name": "Bug", "color": "red" }] } },
740
+ "Progress": { "number": { "format": "percent" } },
741
+ "Notes": { "rich_text": {} },
742
+ "Completed": { "checkbox": {} }
743
+ },
744
+ "icon": { "emoji": "🗂️" },
745
+ "cover": { "external": { "url": "https://..." } }
746
+ }
747
+ }
748
+ ```
749
+ Output: `steps.<ref>.output.id` (database ID)
750
+
751
+ ### GET_DATABASE / UPDATE_DATABASE
752
+ ```json
753
+ { "operation": "GET_DATABASE", "database_id": "{{ steps.create_database.output.id }}" }
754
+ { "operation": "UPDATE_DATABASE", "database_id": "...", "title": [...], "description": [...] }
755
+ ```
756
+ Output: `steps.<ref>.output.title[0].plain_text`
757
+
758
+ ### QUERY_DATABASE
759
+ ```json
760
+ {
761
+ "operation": "QUERY_DATABASE",
762
+ "database_id": "{{ steps.create_database.output.id }}",
763
+ "filter": { "property": "Status", "select": { "equals": "In Progress" } },
764
+ "sorts": [{ "property": "Due Date", "direction": "ascending" }],
765
+ "page_size": 50
766
+ }
767
+ ```
768
+ Filter types: `select`, `multi_select` (`contains`), `date`, `checkbox`, `rich_text`, `number`
769
+ Output: `steps.<ref>.output.results` (array of page objects)
770
+ Record name: `steps.<ref>.output.results[0].properties.Name.title[0].plain_text`
771
+
772
+ ### CREATE_PAGE
773
+ ```json
774
+ {
775
+ "operation": "CREATE_PAGE",
776
+ "parent": { "database_id": "{{ steps.create_database.output.id }}" },
777
+ "properties": {
778
+ "Name": { "title": [{ "text": { "content": "Page Title" } }] },
779
+ "Status": { "select": { "name": "In Progress" } },
780
+ "Tags": { "multi_select": [{ "name": "Feature" }] },
781
+ "Due Date": { "date": { "start": "2025-10-31" } },
782
+ "Progress": { "number": 0.45 },
783
+ "Notes": { "rich_text": [{ "text": { "content": "Note text" } }] },
784
+ "Completed": { "checkbox": false }
785
+ },
786
+ "children": [
787
+ { "object": "block", "type": "heading_2", "heading_2": { "rich_text": [{ "text": { "content": "Section" } }] } },
788
+ { "object": "block", "type": "paragraph", "paragraph": { "rich_text": [{ "text": { "content": "Text" } }] } },
789
+ { "object": "block", "type": "to_do", "to_do": { "rich_text": [{ "text": { "content": "Task" } }], "checked": false } }
790
+ ],
791
+ "icon": { "emoji": "🔐" }
792
+ }
793
+ ```
794
+ Output: `steps.<ref>.output.id` (page ID)
795
+
796
+ ### GET_PAGE / UPDATE_PAGE / DELETE_PAGE / ARCHIVE_PAGE
797
+ ```json
798
+ { "operation": "GET_PAGE", "page_id": "{{ steps.create_page_task_1.output.id }}" }
799
+ { "operation": "UPDATE_PAGE", "page_id": "...", "properties": { ... }, "icon": { "emoji": "🚀" } }
800
+ { "operation": "UPDATE_PAGE", "page_id": "...", "archived": true } // archive
801
+ { "operation": "DELETE_PAGE", "page_id": "..." }
802
+ ```
803
+ Output: `steps.<ref>.output.properties.Notes.rich_text[0].plain_text`
804
+ Archived check: `steps.<ref>.output.archived` (boolean)
805
+ Trashed check: `steps.<ref>.output.in_trash` (boolean)
806
+
807
+ ### GET_PAGE_PROPERTIES
808
+ ```json
809
+ { "operation": "GET_PAGE_PROPERTIES", "page_id": "...", "property_id": "title" }
810
+ ```
811
+
812
+ ### GET_BLOCK / UPDATE_BLOCK / DELETE_BLOCK
813
+ ```json
814
+ { "operation": "GET_BLOCK", "block_id": "{{ steps.create_page_task_1.output.id }}" }
815
+ { "operation": "UPDATE_BLOCK", "block_id": "{{ steps.get_block_children.output.results[0].id }}", "heading_2": { "rich_text": [{ "text": { "content": "Updated" } }] } }
816
+ { "operation": "DELETE_BLOCK", "block_id": "..." }
817
+ ```
818
+ Output GET_BLOCK: `steps.<ref>.output.child_page.title` (for page blocks), `steps.<ref>.output.heading_2.rich_text[0].plain_text` (for heading blocks)
819
+ Trashed check: `steps.<ref>.output.in_trash`
820
+
821
+ ### GET_BLOCK_CHILDREN / APPEND_BLOCK_CHILDREN
822
+ ```json
823
+ { "operation": "GET_BLOCK_CHILDREN", "block_id": "...", "page_size": 100 }
824
+ {
825
+ "operation": "APPEND_BLOCK_CHILDREN",
826
+ "block_id": "...",
827
+ "children": [
828
+ { "object": "block", "type": "divider", "divider": {} },
829
+ { "object": "block", "type": "callout", "callout": { "rich_text": [{ "text": { "content": "Note" } }], "icon": { "emoji": "⚠️" }, "color": "yellow_background" } },
830
+ { "object": "block", "type": "numbered_list_item", "numbered_list_item": { "rich_text": [{ "text": { "content": "Step 1" } }] } },
831
+ { "object": "block", "type": "bulleted_list_item", "bulleted_list_item": { "rich_text": [{ "text": { "content": "Item" } }] } },
832
+ { "object": "block", "type": "quote", "quote": { "rich_text": [{ "text": { "content": "Quote text" } }] } },
833
+ { "object": "block", "type": "toggle", "toggle": { "rich_text": [{ "text": { "content": "Toggle title" } }], "children": [...] } }
834
+ ]
835
+ }
836
+ ```
837
+ Output GET_BLOCK_CHILDREN: `steps.<ref>.output.results` (array), each with `.id`, `.type`
838
+
839
+ ### SEARCH
840
+ ```json
841
+ {
842
+ "operation": "SEARCH",
843
+ "query": "Authentication",
844
+ "filter": { "property": "object", "value": "page" },
845
+ "sort": { "direction": "descending", "timestamp": "last_edited_time" },
846
+ "page_size": 20
847
+ }
848
+ ```
849
+ `filter.value`: `"page"` or `"database"` — omit `filter` to search both
850
+ Output: `steps.<ref>.output.results` (array), `steps.<ref>.output.has_more` (boolean)
851
+
852
+ ### LIST_USERS / GET_USER
853
+ ```json
854
+ { "operation": "LIST_USERS", "page_size": 100 }
855
+ { "operation": "GET_USER", "user_id": "{{ steps.list_users.output.results[0].id }}" }
856
+ ```
857
+ Output LIST_USERS: `steps.<ref>.output.results` (array), each with `.id`, `.type` (`"person"`), `.name`
858
+ Output GET_USER: `steps.<ref>.output.person.email`
859
+
860
+ **Notion rules:**
861
+ - `publishProperties` is `{}` or omitted — all config in `messageBody`
862
+ - `page_id` and `database_id` are 32-char hex strings (no hyphens in Unmeshed, but Notion API accepts both)
863
+ - Block types supported in children: `heading_2`, `heading_3`, `paragraph`, `to_do`, `divider`, `callout`, `numbered_list_item`, `bulleted_list_item`, `quote`, `toggle`
864
+ - `toggle` blocks support `children` array for nested content
865
+ - Use `errorPolicyName: "retry_ep"` on QUERY_DATABASE steps that run soon after mutations
866
+ - `UPDATE_PAGE` with `"archived": true` archives the page (soft delete); `DELETE_PAGE` puts it in trash (`in_trash: true`)
867
+
868
+ ---
869
+
870
+ ## PostgreSQL / MySQL
871
+
872
+ **`input.type`**: `"postgres"` or `"mysql"`
873
+
874
+ ```json
875
+ {
876
+ "type": "postgres",
877
+ "name": "postgres",
878
+ "publishProperties": {
879
+ "action": "EXECUTE_QUERY",
880
+ "query": "INSERT INTO table_name (col1, col2) VALUES ($1, $2)",
881
+ "params": ["{{ steps.<ref>.output.result.field1 }}", "{{ steps.<ref>.output.result.field2 }}"]
882
+ },
883
+ "messageBody": {}
884
+ }
885
+ ```
886
+
887
+ Output: `steps.<ref>.output.result.rows` (SELECT), `steps.<ref>.output.result.rowCount` (INSERT/UPDATE/DELETE)
888
+
889
+ **Rules:**
890
+ - Always parameterise queries — `$1`, `$2` for Postgres; `?` for MySQL
891
+ - `params` is always an array of strings/values
892
+
893
+ ---
894
+
895
+ ## HTTP / REST API (as INTEGRATION)
896
+
897
+ **`input.type`**: `"http"`
898
+
899
+ ```json
900
+ {
901
+ "type": "http",
902
+ "name": "http",
903
+ "publishProperties": {
904
+ "method": "POST",
905
+ "url": "https://api.example.com/endpoint",
906
+ "headers": { "Content-Type": "application/json", "Authorization": "Bearer <token>" }
907
+ },
908
+ "messageBody": { "field1": "{{ steps.<ref>.output.result.value }}" }
909
+ }
910
+ ```
911
+
912
+ Output: `steps.<ref>.output.result` (response body), `steps.<ref>.output.status` (HTTP status code)
913
+
914
+ **Note:** There is also a native `HTTP` step type (uppercase) with polling support — see step-types.md.