if or loop to render or repeat the whole set at once. The type value is "group".
When to use:
- Apply conditional logic to multiple elements at once
- Loop over multiple elements together
- Organize related elements into logical sections
- Create reusable element blocks
{
"type": "group",
"elements": [
{
"type": "text",
"content": "Welcome!"
},
{
"type": "action",
"content": "Get Started",
"href": "https://example.com/start"
}
]
}
const { requestId } = await client.send.message({
message: {
to: {
email: "sarah@acme-corp.com",
},
content: {
version: "2022-01-01",
elements: [
{
type: "group",
elements: [
{
type: "text",
content: "Welcome!",
},
{
type: "action",
content: "Get Started",
href: "https://example.com/start",
},
],
},
],
},
},
});
response = client.send.message(
message={
"to": {
"email": "sarah@acme-corp.com",
},
"content": {
"version": "2022-01-01",
"elements": [
{
"type": "group",
"elements": [
{
"type": "text",
"content": "Welcome!",
},
{
"type": "action",
"content": "Get Started",
"href": "https://example.com/start",
},
],
},
],
},
},
)
curl -X POST https://api.courier.com/send \
-H "Authorization: Bearer $COURIER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": {
"to": {
"email": "sarah@acme-corp.com"
},
"content": {
"version": "2022-01-01",
"elements": [
{
"type": "group",
"elements": [
{
"type": "text",
"content": "Welcome!"
},
{
"type": "action",
"content": "Get Started",
"href": "https://example.com/start"
}
]
}
]
}
}
}'
response = courier.send_.message(
message: {
to: {
email: "sarah@acme-corp.com"
},
content: {
version: "2022-01-01",
elements: [
{
type: "group",
elements: [
{
type: "text",
content: "Welcome!"
},
{
type: "action",
content: "Get Started",
href: "https://example.com/start"
}
]
}
]
}
}
)
// Elemental nodes carry no typed content fields, so pass the document as raw JSON.
content := param.Override[shared.ElementalContentParam](json.RawMessage(`{
"version": "2022-01-01",
"elements": [
{
"type": "group",
"elements": [
{
"type": "text",
"content": "Welcome!"
},
{
"type": "action",
"content": "Get Started",
"href": "https://example.com/start"
}
]
}
]
}`))
response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
Message: courier.SendMessageParamsMessage{
To: courier.SendMessageParamsMessageToUnion{
OfUserRecipient: &shared.UserRecipientParam{
Email: courier.String("sarah@acme-corp.com"),
},
},
Content: courier.SendMessageParamsMessageContentUnion{OfElementalContent: &content},
},
})
SendMessageParams params = SendMessageParams.builder()
.message(SendMessageParams.Message.builder()
.to(JsonValue.from(java.util.Map.of("email", "sarah@acme-corp.com")))
.content(JsonValue.from(java.util.Map.of(
"version", "2022-01-01",
"elements", java.util.List.of(
java.util.Map.of(
"type", "group",
"elements", java.util.List.of(
java.util.Map.of(
"type", "text",
"content", "Welcome!"),
java.util.Map.of(
"type", "action",
"content", "Get Started",
"href", "https://example.com/start")))))))
.build())
.build();
SendMessageResponse response = client.send().message(params);
$response = $client->send->message(
message: [
'to' => [
'email' => 'sarah@acme-corp.com',
],
'content' => [
'version' => '2022-01-01',
'elements' => [
[
'type' => 'group',
'elements' => [
[
'type' => 'text',
'content' => 'Welcome!',
],
[
'type' => 'action',
'content' => 'Get Started',
'href' => 'https://example.com/start',
],
],
],
],
],
],
);
// Elemental nodes expose no typed content fields, so build the document from raw JSON.
SendMessageParams parameters = new()
{
Message = new()
{
To = new UserRecipient { Email = "sarah@acme-corp.com" },
Content = ElementalContent.FromRawUnchecked(
JsonSerializer.Deserialize<Dictionary<string, JsonElement>>("""
{
"version": "2022-01-01",
"elements": [
{
"type": "group",
"elements": [
{
"type": "text",
"content": "Welcome!"
},
{
"type": "action",
"content": "Get Started",
"href": "https://example.com/start"
}
]
}
]
}
""")),
},
};
var response = await client.Send.Message(parameters);
courier send message \
--api-key "$COURIER_API_KEY" \
--message.to '{"email": "sarah@acme-corp.com"}' \
--message.content '{"version": "2022-01-01", "elements": [{"type": "group", "elements": [{"type": "text", "content": "Welcome!"}, {"type": "action", "content": "Get Started", "href": "https://example.com/start"}]}]}'
With Courier MCP, send an email to sarah@acme-corp.com with a grouped heading and button block.
CourierElement[]
required
An array of Elemental elements. They render together as one unit.
string | object[]
A condition that determines whether the whole group renders. Accepts a string expression or a structured condition array. See .
string
An expression that repeats the whole group. See .
string
A reference identifier for the group. See .
string[]
An array of channel names. The group renders only for the listed channels. See .
{
"type": "group",
"if": "{{user.plan}} === 'premium'",
"elements": [
{
"type": "text",
"content": "**Premium Features**",
"text_style": "h2"
},
{
"type": "text",
"content": "You have access to all premium features!"
},
{
"type": "action",
"content": "Explore Features",
"href": "https://example.com/premium"
}
]
}
{
"type": "group",
"loop": "data.products",
"elements": [
{
"type": "text",
"content": "# {{$.item.name}}",
"text_style": "h2"
},
{
"type": "divider"
},
{
"type": "text",
"content": "Description: {{$.item.description}}"
},
{
"type": "text",
"content": "Price: ${{$.item.price}}",
"bold": true
},
{
"type": "action",
"content": "View Product",
"href": "https://example.com/products/{{$.item.id}}"
}
]
}
{
"type": "group",
"if": "{{order.items.length}} > 0",
"elements": [
{
"type": "text",
"content": "Order Items",
"text_style": "h2"
},
{
"type": "group",
"loop": "data.order.items",
"elements": [
{
"type": "text",
"content": "{{$.item.name}} - ${{$.item.price}}"
}
]
},
{
"type": "text",
"content": "Total: ${{order.total}}",
"bold": true
}
]
}