Send Slack Message
Post a message to a Slack channel or DM, optionally with Block Kit formatting
Source Code
import fs from "fs";
const [channelId, message, blocksPath = ""] = process.argv.slice(2);
if (!channelId) {
console.error("Error: channelId is required");
process.exit(1);
}
if (!message) {
console.error("Error: message is required (used as fallback text)");
process.exit(1);
}
// Extract JSON from content - handles both raw JSON and markdown with code blocks
function extractBlocksJson(content) {
const trimmed = content.trim();
// If it starts with { or [, it's raw JSON
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
return JSON.parse(trimmed);
}
// Otherwise, look for JSON in markdown code blocks
// Match ```json ... ``` or ``` ... ``` containing JSON
const codeBlockMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
if (codeBlockMatch) {
const codeContent = codeBlockMatch[1].trim();
if (codeContent.startsWith("{") || codeContent.startsWith("[")) {
return JSON.parse(codeContent);
}
}
// Try to find any JSON object in the content
const jsonMatch = trimmed.match(/(\{[\s\S]*"blocks"[\s\S]*\})/);
if (jsonMatch) {
return JSON.parse(jsonMatch[1]);
}
throw new Error("No valid Block Kit JSON found in file");
}
console.log(`Sending message to ${channelId}...`);
try {
const body = {
channel: channelId,
text: message,
};
// Load blocks from file if provided
if (blocksPath && blocksPath.trim() && blocksPath !== "undefined") {
try {
const fileContent = fs.readFileSync(blocksPath, "utf-8");
const parsed = extractBlocksJson(fileContent);
// Accept either { blocks: [...] } or just [...]
body.blocks = Array.isArray(parsed) ? parsed : parsed.blocks;
console.log(
` Including ${body.blocks.length} Block Kit blocks from ${blocksPath}`
);
} catch (err) {
if (err.code === "ENOENT") {
console.error(`Warning: Blocks file not found: ${blocksPath}`);
} else {
console.error(`Warning: Failed to parse blocks: ${err.message}`);
}
console.log(" Sending plain text only");
}
}
const res = await fetch("https://slack.com/api/chat.postMessage", {
method: "POST",
headers: {
Authorization: "Bearer PLACEHOLDER_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!res.ok) {
throw new Error(`Slack API HTTP error: ${res.status}`);
}
const data = await res.json();
if (!data.ok) {
console.error(`Slack API error: ${data.error}`);
if (data.error === "channel_not_found") {
console.error(" The channel ID may be invalid or the bot lacks access");
} else if (data.error === "not_in_channel") {
console.error(" The bot needs to be added to this channel first");
} else if (data.error === "invalid_blocks") {
console.error(" The Block Kit JSON is malformed");
console.error(" Test at: https://app.slack.com/block-kit-builder");
}
throw new Error(`Slack API error: ${data.error}`);
}
const timestamp = data.ts;
const sentChannel = data.channel;
console.log(`\n✓ Message sent successfully`);
console.log(` Channel: ${sentChannel}`);
console.log(` Timestamp: ${timestamp}`);
console.log(
` Time: ${new Date(parseFloat(timestamp) * 1000).toISOString()}`
);
console.log(
` Format: ${
body.blocks ? `Block Kit (${body.blocks.length} blocks)` : "Plain text"
}`
);
console.log(
JSON.stringify({
success: true,
channel: sentChannel,
ts: timestamp,
hasBlocks: !!body.blocks,
})
);
} catch (error) {
console.error("Failed to send message:", error.message);
throw error;
} import fs from "fs";
const [channelId, message, blocksPath = ""] = process.argv.slice(2);
if (!channelId) {
console.error("Error: channelId is required");
process.exit(1);
}
if (!message) {
console.error("Error: message is required (used as fallback text)");
process.exit(1);
}
// Extract JSON from content - handles both raw JSON and markdown with code blocks
function extractBlocksJson(content) {
const trimmed = content.trim();
// If it starts with { or [, it's raw JSON
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
return JSON.parse(trimmed);
}
// Otherwise, look for JSON in markdown code blocks
// Match ```json ... ``` or ``` ... ``` containing JSON
const codeBlockMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
if (codeBlockMatch) {
const codeContent = codeBlockMatch[1].trim();
if (codeContent.startsWith("{") || codeContent.startsWith("[")) {
return JSON.parse(codeContent);
}
}
// Try to find any JSON object in the content
const jsonMatch = trimmed.match(/(\{[\s\S]*"blocks"[\s\S]*\})/);
if (jsonMatch) {
return JSON.parse(jsonMatch[1]);
}
throw new Error("No valid Block Kit JSON found in file");
}
console.log(`Sending message to ${channelId}...`);
try {
const body = {
channel: channelId,
text: message,
};
// Load blocks from file if provided
if (blocksPath && blocksPath.trim() && blocksPath !== "undefined") {
try {
const fileContent = fs.readFileSync(blocksPath, "utf-8");
const parsed = extractBlocksJson(fileContent);
// Accept either { blocks: [...] } or just [...]
body.blocks = Array.isArray(parsed) ? parsed : parsed.blocks;
console.log(
` Including ${body.blocks.length} Block Kit blocks from ${blocksPath}`
);
} catch (err) {
if (err.code === "ENOENT") {
console.error(`Warning: Blocks file not found: ${blocksPath}`);
} else {
console.error(`Warning: Failed to parse blocks: ${err.message}`);
}
console.log(" Sending plain text only");
}
}
const res = await fetch("https://slack.com/api/chat.postMessage", {
method: "POST",
headers: {
Authorization: "Bearer PLACEHOLDER_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!res.ok) {
throw new Error(`Slack API HTTP error: ${res.status}`);
}
const data = await res.json();
if (!data.ok) {
console.error(`Slack API error: ${data.error}`);
if (data.error === "channel_not_found") {
console.error(" The channel ID may be invalid or the bot lacks access");
} else if (data.error === "not_in_channel") {
console.error(" The bot needs to be added to this channel first");
} else if (data.error === "invalid_blocks") {
console.error(" The Block Kit JSON is malformed");
console.error(" Test at: https://app.slack.com/block-kit-builder");
}
throw new Error(`Slack API error: ${data.error}`);
}
const timestamp = data.ts;
const sentChannel = data.channel;
console.log(`\n✓ Message sent successfully`);
console.log(` Channel: ${sentChannel}`);
console.log(` Timestamp: ${timestamp}`);
console.log(
` Time: ${new Date(parseFloat(timestamp) * 1000).toISOString()}`
);
console.log(
` Format: ${
body.blocks ? `Block Kit (${body.blocks.length} blocks)` : "Plain text"
}`
);
console.log(
JSON.stringify({
success: true,
channel: sentChannel,
ts: timestamp,
hasBlocks: !!body.blocks,
})
);
} catch (error) {
console.error("Failed to send message:", error.message);
throw error;
}