In the world of automated workflows and agentic systems, real-time feedback is not just a luxury; it's a necessity. Whether you're tracking new user sign-ups, monitoring system health, or celebrating a completed sale, instant notifications are key to staying informed. Slack has become the de facto command center for many teams, making it the perfect destination for these critical alerts.
But how do you build a notification system that is reliable, reusable, and seamlessly integrates into your larger business processes?
Enter action.do. Instead of writing one-off scripts or managing complex serverless functions, you can define your notification logic as an atomic action—a granular, single-responsibility task that executes flawlessly as a powerful API endpoint.
Today, we'll walk through how to build a robust send-slack-message action using action.do, transforming a simple task into a cornerstone of your "Business-as-Code" strategy.
Before we dive in, let's clarify what we mean by an "atomic action" in the context of action.do.
An atomic action is a self-contained, single-responsibility task designed to either complete successfully or fail entirely, leaving no messy partial states. It's the smallest indivisible unit of work in your workflow.
Think of actions like:
This "all or nothing" principle is what makes your automations robust. You can be certain a notification was sent, or you'll know precisely why it failed, allowing for automated retries or escalations.
To follow along, you'll need:
First, we need to give our action a secure way to post messages to a specific Slack channel. Slack's Incoming Webhooks are perfect for this.
This is where the "Business-as-Code" philosophy shines. On the action.do platform, you define the logic for your action. This includes its name, its expected inputs, and the code to execute.
Let's define our action conceptually.
/*
This represents the code you would write inside the action.do
platform for the 'send-slack-message' action.
*/
import { fetch } from 'undici';
// The handler function that action.do runs
export default async function handler({ input, secrets }) {
// Securely access the webhook URL from environment secrets
const SLACK_WEBHOOK_URL = secrets.SLACK_WEBHOOK_URL;
const { text, channel, username } = input;
if (!text) {
throw new Error('Input validation failed: "text" is a required input.');
}
// Construct the payload for the Slack API
const payload = {
text,
channel, // Slack uses the webhook's default if this is omitted
username: username || 'Do Bot',
};
const response = await fetch(SLACK_WEBHOOK_URL, {
method: 'POST',
body: JSON.stringify(payload),
headers: { 'Content-Type': 'application/json' },
});
if (!response.ok) {
const errorText = await response.text();
// Throwing an error ensures the action fails atomically
throw new Error(`Slack API request failed: ${errorText}`);
}
// A successful return object signals completion
return { success: true, message: 'Notification sent successfully.' };
}
By defining this once, you now have a hardened, reusable API endpoint for sending any Slack message, available across all your applications.
With the send-slack-message action defined and waiting, calling it from your application codebase is incredibly simple. Using the @do-sdk/core, you can trigger it with a clean, declarative call.
Here’s how you might use it in a TypeScript application to announce a new user sign-up.
import { Dō } from '@do-sdk/core';
// Initialize the SDK with your API key
const dō = new Dō({ apiKey: 'YOUR_API_KEY' });
async function notifyOnNewUser(userId: string) {
console.log(`Triggering Slack notification for new user: ${userId}`);
try {
// Define and execute the single, atomic action
const result = await dō.action.execute({
name: 'send-slack-message',
input: {
channel: '#signups',
username: 'Signup Bot',
text: `🎉 A new user just joined! Welcome user-id: ${userId}`,
},
});
console.log('Action executed successfully!', result);
// { success: true, transactionId: 'txn_abc_123' }
} catch (error) {
console.error('Failed to execute Slack notification action:', error);
}
}
// Example usage within your application's logic
notifyOnNewUser('user-b2d9-4c7a');
That's it! You've successfully decoupled your notification logic from your core application code.
The real power of action.do is revealed when you start composing these atomic actions into larger, more complex processes using workflow.do. Our send-slack-message action is no longer just a utility; it's a fundamental building block.
Consider a complete new user onboarding workflow:
By chaining these single-responsibility actions, you create a robust, observable, and easily maintainable service. If the email fails, it doesn't stop the Slack message. If the database write fails, the entire workflow stops before side effects occur, thanks to the transactional integrity of atomic actions.
While a serverless function can achieve a similar outcome, action.do is a higher-level abstraction designed specifically for business logic and agentic workflows. It provides:
You've just built your first atomic notification service—a reusable, reliable, and scalable component for all your automation needs. By thinking in terms of small, composable actions, you lay the foundation for powerful "Business-as-Code" that can grow and adapt with your organization.
Ready to build more robust automations? Get started with action.do and turn your business logic into powerful API endpoints.