Docs / Strand / connectors/contact-message
Contact Entity
Direction: Read/Write | Type: contact.platform
Send messages and files to Contact entities and update entity state tables from your Strand workflows.
The contact.platform connector communicates with the Contact platform internally; no external access or public URLs are required.
Connector Setup#
Create a Contact Entity Message connector from the Connectors page.
| Field | Required | Description |
|---|---|---|
| API Key | Yes | Entity API key (PASETO token). The entity's role must include the appropriate permissions for the operations you plan to use. |
The API key is encrypted at rest.
Tip: Store entity API keys in the Strand vault and reference them with {{ vault.contact_api_key }} in the connector configuration to keep credentials centralized.
Operations#
The connector's Operation dropdown offers two operations: send (default) and update_state. Sending files is not an Operation of this connector — it uses a separate node type (Contact: Send File, added from the node palette), documented under Sending files below.
send (default)#
Send a message to a Contact entity. This is the default operation if none is specified.
Node Configuration#
| Field | Required | Description |
|---|---|---|
| Operation | No | Set to send (or leave blank; it is the default) |
| Data | Yes | Message payload as a JSON object. Values support Jinja2 templates. |
Destination (key: dest) |
No | Target entity name or full resource path. Supports Jinja2 templates. |
| Tags | No | List of string tags for message routing and flow triggering. |
Data is the message payload:
{
"temperature": "{{ payload.temp }}",
"status": "processed",
"source": "{{ initial.meta.workflow_id }}"
}
Destination supports two formats:
- Entity name: for same-account delivery (e.g.,
my-sensor). The entity is looked up by name within the sending entity's account. - Full resource path: for cross-account delivery (e.g.,
12345:us-east-1:entity:my-sensor).
If omitted, the message is published without a specific destination.
Tags are used for routing and triggering Contact flows:
["alert", "high-priority", "sensor-data"]
send Example#
{
"operation": "send",
"data": {
"temperature": "{{ payload.temp }}",
"humidity": "{{ payload.humidity }}",
"timestamp": "{{ payload.ts }}"
},
"dest": "sensor-gateway",
"tags": ["sensor-data", "building-a"]
}
send Permissions#
The entity whose API key is used must have a role with the entity:WriteMessages action. If a destination is specified, the entity also needs entity:WriteMessages permission scoped to the destination's resource path.
---
Fetching files (Contact: Fetch File node)#
Fetching a tag-routed file's bytes is a separate node type — Contact: Fetch File (platform.contact.fetch_file), added from the node palette. It reads a file Contact routed to your workflow and returns the bytes base64-encoded, typically to chain into a Contact: Send File node, an HTTP upload, or a Surface scan. It authenticates with the platform's internal token, so it needs no entity API key.
Single-consume: a successful fetch reclaims the file and its sender's storage. Fetch each routed file exactly once.
Node Configuration#
| Field | Required | Description |
|---|---|---|
Transfer ID (key: transfer_id) |
Yes | Transfer ID of the file to fetch. Defaults to {{ payload.data.transfer_id }} so it chains straight from the trigger. |
Size (key: size) |
No | Declared file size, used to refuse oversized files before fetching. Defaults to {{ payload.data.size }} from the trigger. |
Max Inline Bytes (key: max_inline_bytes) |
No | Maximum bytes to inline as base64 (1 KB – 10 MB, default 10 MB). A larger file is refused before the fetch, so a file too big to inline never consumes its storage. |
| Timeout | No | Request timeout in seconds (5–120, default 30). |
On success the node adds the file bytes as content_base64 (plus size) to the output — which is exactly what a downstream Contact: Send File node reads by default.
Fetch File Example#
Fetch a file the trigger routed in, ready to hand to a Send File node downstream:
{
"transfer_id": "{{ payload.data.transfer_id }}",
"size": "{{ payload.data.size }}",
"max_inline_bytes": 10485760
}
---
Sending files (Contact: Send File node)#
Sending a file is a separate node type — Contact: Send File (platform.contact.send_file), added from the node palette — not an operation of this connector. It sends a file into Contact (file transfer). The file bytes are supplied base64-encoded, typically chained from a Contact: Fetch File node or an HTTP download. The sending entity pays storage until the file is consumed.
Node Configuration#
| Field | Required | Description |
|---|---|---|
Content (key: content_base64) |
Yes | File bytes, base64-encoded. Defaults to {{ payload.content_base64 }} so it chains straight from a Fetch File node. |
File Name (key: file_name) |
No | File name including extension (drives Contact's file-type allowlist). |
Content Type (key: content_type) |
No | MIME type; inferred from the extension if omitted. |
Destination (key: dest) |
No | Recipient entity name or full resource path (direct / group / cross-account). |
| Tags | No | Comma-separated routing tags that route the file to subscribing Strand workflows/connectors. |
Kind (key: kind) |
No | Set to clip for the motion gallery (no recipient). |
Meta (key: meta) |
No | Optional sender metadata, ≤8 KB. Supports Jinja2 templates. |
Set exactly one delivery target: dest, tags, or kind: clip. See Delivery modes for how each routes.
send_file Example#
Forward a fetched file to a specific device (direct delivery):
{
"content_base64": "{{ payload.content_base64 }}",
"file_name": "{{ payload.file_name }}",
"content_type": "{{ payload.content_type }}",
"dest": "gateway-01"
}
On success the node adds transfer_id, sha256, size, and mode to the output.
send_file Permissions#
The entity whose API key is used must have a role with the entity:SendFiles action. For direct, group, and cross-account delivery the entity also needs entity:SendFiles scoped to the destination's resource path.
---
update_state#
Update an entity's status table. The status table is a JSON key-value store attached to each Contact entity, useful for tracking device state, configuration, or metadata.
Node Configuration#
| Field | Required | Description |
|---|---|---|
| Operation | Yes | Set to update_state |
| Entity | Yes | Entity name whose state table to update. Supports Jinja2 templates. |
| State | Yes | JSON object (or Jinja2 template that renders to JSON) containing the state data to apply. |
| Replace | No | false (default) to merge (PATCH): existing keys are preserved, provided keys are added or overwritten. true to replace (PUT): the entire state table is overwritten with the provided data. |
update_state Example#
Merge new sensor readings into an entity's state table, preserving existing keys:
{
"operation": "update_state",
"entity": "{{ initial.meta.entity_name }}",
"state": {
"last_temperature": "{{ payload.temp }}",
"last_seen": "{{ payload.timestamp }}",
"firmware": "{{ payload.fw_version }}"
},
"replace": "false"
}
Replace the entire state table (all previous keys are removed):
{
"operation": "update_state",
"entity": "thermostat-01",
"state": {
"mode": "cooling",
"target_temp": 22,
"updated_by": "{{ initial.meta.workflow_id }}"
},
"replace": "true"
}
Important: Whenreplaceis"true", any keys not included instatewill be deleted from the entity's status table. Use"false"(the default) to safely merge updates.
update_state Permissions#
The entity whose API key is used must have a role with the entity:UpdateEntityStatusTable action.
Errors#
The connector translates Contact API responses into descriptive messages:
send Errors#
| Status | Meaning |
|---|---|
| 401 | Invalid or expired entity API key, or the key does not belong to an entity |
| 403 | Entity is missing the entity:WriteMessages permission |
| 429 | Monthly data limit exceeded for this account |
| Connection error | Contact service is unreachable. Contact your administrator if this persists. |
send_file Errors#
| Status | Meaning |
|---|---|
| 400 | No consumer for the tags, invalid destination, or the group has no same-account members |
| 401 | Invalid or expired entity API key |
| 402 | Surface monthly scan quota exhausted for this account |
| 403 | Entity is missing the entity:SendFiles permission, or file transfer is disabled |
| 413 | File exceeds the size limit or the account's monthly data limit |
| 415 | File type not allowed |
| 422 | File blocked by Surface (flagged malicious) |
| Connection error | Contact service is unreachable. Contact your administrator if this persists. |
update_state Errors#
| Status | Meaning |
|---|---|
| 401 | Invalid or expired entity API key, or the key does not belong to an entity |
| 403 | Entity is missing the entity:UpdateEntityStatusTable permission |
| 404 | Entity not found |
| 429 | Monthly data limit exceeded for this account |
| 500 | Contact service encountered an internal error |
| Connection error | Contact service is unreachable. Contact your administrator if this persists. |
Tendrl