You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs-src/spectrum-ts/content.mdx.vel
+154-4Lines changed: 154 additions & 4 deletions
Original file line number
Diff line number
Diff line change
@@ -1,14 +1,14 @@
1
1
---
2
2
title: "Content"
3
-
description: "Build text, attachments, and platform-specific content for outgoing messages"
3
+
description: "Build text, attachments, voice, contacts, polls, rich links, groups, and platform-specific content for outgoing messages"
4
4
---
5
5
6
6
import { TypeTooltip } from "/snippets/type-tooltip.mdx";
7
7
8
8
{% set ci = symbol("ts:spectrum-ts#ContentInput") %}
9
9
{% set cb = symbol("ts:spectrum-ts#ContentBuilder") %}
10
10
11
-
Spectrum provides three content builders — `text`, `attachment`, and `custom` — plus a string shortcut that's equivalent to `text()`. Any API that takes a <TypeTooltip name="ContentInput" type={`{{ ci.signature }}`} /> accepts a plain string or a <TypeTooltip name="ContentBuilder" type={`{{ cb.signature }}`} />.
11
+
Spectrum exposes a family of content builders — `text`, `attachment`, `voice`, `contact`, `richlink`, `poll`, `group`, and `custom` — plus a string shortcut that's equivalent to `text()`. Any API that takes a <TypeTooltip name="ContentInput" type={`{{ ci.signature }}`} /> accepts a plain string or a <TypeTooltip name="ContentBuilder" type={`{{ cb.signature }}`} />.
If the MIME type can't be inferred from the name and you didn't pass `options.mimeType`, `attachment()` throws when the content is built.
42
42
43
+
## Voice
44
+
45
+
Send a voice note. Same input shape as `attachment` — a path or a `Buffer` plus optional metadata.
46
+
47
+
```ts
48
+
import { voice } from "spectrum-ts";
49
+
50
+
// From a file path
51
+
await space.send(voice("/path/to/note.m4a"));
52
+
53
+
// From a buffer — duration in seconds is optional but useful for waveform UIs
54
+
await space.send(voice(buffer, {
55
+
name: "note.m4a",
56
+
mimeType: "audio/mp4",
57
+
duration: 12,
58
+
}));
59
+
```
60
+
61
+
Platforms that don't support voice notes typically downgrade to a regular audio attachment. If the MIME type can't be inferred and `options.mimeType` is omitted, the builder throws at send time.
62
+
63
+
<Accordion title="Voice" description="Resolved voice content delivered alongside the message.">
64
+
| Field | Type | Description |
65
+
|---|---|---|
66
+
| `mimeType` | `string` | The audio MIME type (e.g. `audio/mp4`). |
67
+
| `name` | `string` (optional) | Filename for the underlying clip. |
68
+
| `duration` | `number` (optional) | Length in seconds. |
69
+
| `size` | `number` (optional) | Byte length, when known up front. |
| `stream()` | `() => Promise<ReadableStream>` | Stream the bytes — preferred for large clips. |
72
+
</Accordion>
73
+
74
+
## Contacts
75
+
76
+
Share contact cards. The `contact()` builder takes either a structured `ContactInput`, a vCard string, a `vcf` instance, or a known `User` paired with optional `ContactDetails`.
// Or parse first if you want to inspect/edit the fields
109
+
const parsed = fromVCard(vcf);
110
+
await space.send(contact({ ...parsed, note: "Met at conference" }));
111
+
```
112
+
</Tab>
113
+
</Tabs>
114
+
115
+
`fromVCard(vcf)` parses a vCard string into a `ContactInput`; `toVCard(contact)` serializes a resolved `Contact` back to vCard.
116
+
117
+
<Accordion title="ContactInput" description="The fields you can populate on a contact card. All fields are optional except where the receiving platform requires at least one identifying field.">
Render a URL as a rich preview card with title, summary, and cover image. Spectrum scrapes Open Graph metadata at send time; pass just the URL and the builder fills in the rest.
`title()`, `summary()`, and `cover()` are lazy async accessors — the metadata fetch happens only if the receiving platform needs it. Platforms without rich-link support fall back to the URL as plain text.
143
+
144
+
<Accordion title="Richlink" description="Resolved rich-link content with lazy metadata accessors.">
{% set pollChoiceInput = symbol("ts:spectrum-ts#PollChoiceInput") %}
156
+
157
+
Send a poll with a title and a list of choices. Each choice can be a plain string or a <TypeTooltip name="PollChoiceInput" type={`{{ pollChoiceInput.signature }}`} /> object — use `option()` when you want the explicit form.
// Or an array, optionally using option() for clarity
166
+
await space.send(poll("Lunch?", [
167
+
option("Pizza"),
168
+
option("Sushi"),
169
+
option("Tacos"),
170
+
]));
171
+
```
172
+
173
+
Poll responses arrive as `poll_option` content — see [Messages](/spectrum-ts/messages) for narrowing on incoming votes.
174
+
175
+
## Groups
176
+
177
+
{% set groupType = symbol("ts:spectrum-ts#Group") %}
178
+
179
+
A `group` bundles multiple messages into one logical unit (an album of images, a multi-attachment reply). Each item is delivered as its own `Message`, but they ship together so the receiving platform can render them as a single visual group when supported.
180
+
181
+
```ts
182
+
import { group, attachment } from "spectrum-ts";
183
+
184
+
await space.send(group(
185
+
attachment("/path/to/photo-1.jpg"),
186
+
attachment("/path/to/photo-2.jpg"),
187
+
attachment("/path/to/photo-3.jpg"),
188
+
));
189
+
```
190
+
191
+
Groups don't nest, and reactions can't be group members — the builder enforces both at construction time. Platforms that don't support grouping fall back to sending each item sequentially.
192
+
43
193
## Custom
44
194
45
-
Send structured, platform-specific payloads. Use this when the receiving platform supports rich content types that don't fit into text or attachments.
195
+
Send structured, platform-specific payloads. Use this when the receiving platform supports rich content types that don't fit into the built-in builders.
46
196
47
197
```ts
48
198
import { custom } from "spectrum-ts";
@@ -63,7 +213,7 @@ await space.send(
63
213
);
64
214
```
65
215
66
-
This runs one `send()` per item on the underlying provider — not a single compound message.
216
+
This runs one `send()` per item on the underlying provider — not a single compound message. Reach for `group(...)` instead when you specifically want them rendered as one bundled unit.
console.log(`group of ${message.content.items.length} items`);
106
+
break;
86
107
case "custom":
87
108
console.log(message.content.raw);
88
109
break;
89
110
}
90
111
}
91
112
```
92
113
93
-
<Accordion title="Content" description="The incoming content variants that every Message carries.">
114
+
<Accordion title="Content" description="The incoming content variants that every Message carries. Most platforms only emit a subset — narrow defensively.">
| `"poll_option"` | `option: { title }`, `poll: Poll`, `selected: boolean`, `title: string` — sent as a vote |
125
+
| `"group"` | `items: Message[]` — bundled multi-message unit |
98
126
| `"custom"` | `raw: unknown` — platform-specific structured data |
99
127
</Accordion>
100
128
129
+
Outgoing-only variants like `"effect"` (an iMessage screen effect wrapping inner content) appear on messages you sent and are echoed by the platform; see [iMessage](/spectrum-ts/providers/imessage) for the builder.
130
+
101
131
## Filtering out your own messages
102
132
103
133
On platforms where your account also receives its own sends, guard with a platform-specific check — for example, iMessage carries an `isFromMe` flag on the raw message extra you can expose through a provider `message.schema`.
Copy file name to clipboardExpand all lines: docs-src/spectrum-ts/providers/imessage.mdx.vel
+37Lines changed: 37 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -124,6 +124,43 @@ When omitted, Spectrum picks a phone at random from the available dedicated line
124
124
Per-phone routing applies to dedicated lines (Business plan) only. On shared-pool plans the `phone` parameter is ignored — all conversations route through the shared pool automatically.
125
125
</Note>
126
126
127
+
## Message effects
128
+
129
+
iMessage supports bubble effects (sent message animation) and screen effects (full-screen animation on receive). Wrap any content with `effect()`:
130
+
131
+
```ts
132
+
import { effect, imessage } from "spectrum-ts/providers/imessage";
0 commit comments