In any modern business, communication is key. Whether it's a new lead in your CRM, a completed payment, or a critical system alert, getting the right information to the right people at the right time is crucial. Workflows and automations often end with a notification, and for many teams, the hub for that real-time information is Slack.
But building notification logic directly into every single application or script is inefficient. It leads to duplicated code, maintenance headaches, and a lack of centralized control. What if you could build your Slack notification logic once and reuse it across any workflow?
This is the power of the .do platform. By encapsulating tasks into atomic, reusable units, you can build robust and scalable systems. In this guide, we'll walk you through a practical example: creating a centralized Slack notification service using a single action.do.
Before we dive into the code, let's understand why using an action.do for this is a game-changer. An atomic action is the smallest indivisible unit of work on the .do platform. It's a self-contained function designed to do one thing and do it well.
For our Slack notifier, this means:
To follow along, you'll need two things:
The first step is to define the "what" and the "how" of our action. We'll give it a name, describe what it does, define its inputs, and then provide the handler code that performs the logic.
We'll use the @do-platform/sdk to create our new action. The action will take a single string input, message, and send it to the Slack channel configured in our webhook.
import { Do } from '@do-platform/sdk';
import fetch from 'node-fetch'; // Or any other HTTP client
// Initialize the .do client with your API key
const-do = new Do(process.env.DO_API_KEY);
// Retrieve the secure webhook URL from environment variables
const slackWebhookUrl = process.env.SLACK_WEBHOOK_URL;
if (!slackWebhookUrl) {
throw new Error('SLACK_WEBHOOK_URL environment variable not set.');
}
// Define our new atomic action for sending Slack messages
const slackNotifierAction = await-do.action.create({
name: 'send-slack-message',
description: 'Sends a formatted message to a predefined Slack channel via webhook.',
inputs: {
message: 'string', // The text content of the message to send
},
handler: async (inputs) => {
try {
const response = await fetch(slackWebhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ text: inputs.message }),
});
if (!response.ok) {
// Slack API did not return a 2xx response
const errorText = await response.text();
console.error('Slack API error:', errorText);
return { success: false, error: `Slack API returned status ${response.status}` };
}
// The message was sent successfully!
return { success: true };
} catch (error) {
console.error('Failed to send Slack message:', error);
return { success: false, error: error.message };
}
}
});
console.log('Action created:', slackNotifierAction.id);
With this code, we've registered a new, reusable building block in our .do environment. The platform now knows about an action named send-slack-message, understands its inputs, and has the versioned code ready to execute on demand.
Defining an action is only half the story. Its true power is unlocked when it's executed as a step in a larger agentic workflow.
As a core principle, actions on the .do platform are atomic and don't call each other. They are orchestrated by higher-level .workflow.do agents.
Imagine a simple user onboarding workflow. The steps might be:
Executing our new action within that workflow would be as simple as this:
// This is a conceptual example of a step within a larger workflow.
// Your workflow agent would orchestrate these calls.
const newUserEmail = 'new-customer@example.com';
// ...previous steps in the workflow succeed...
console.log('Notifying team about new signup...');
// Execute our reusable Slack action
const notificationResult = await-do.action.run('send-slack-message', {
message: `🎉 New user signed up! Email: ${newUserEmail}`
});
if (notificationResult.success) {
console.log('Slack notification sent successfully.');
} else {
console.error('Failed to send Slack notification:', notificationResult.error);
}
Notice how clean this is. The orchestration logic doesn't need to know anything about HTTP requests, webhook URLs, or JSON payloads. It simply invokes the named action with the required inputs. Our send-slack-message action is now a reliable, observable, and composable tool in our automation arsenal.
You've just built more than a simple notifier; you've created a piece of Services-as-Software. This atomic action is a durable, managed component that can be composed into countless business processes.
By building your automation on a foundation of small, single-purpose actions, you create a system that is easier to understand, debug, and scale. This is the future of agentic workflows.
Ready to start building your own atomic units of automation? Define. Execute. Repeat.