In the world of workflow automation, complexity is the enemy of reliability. As we build increasingly sophisticated systems to run our businesses, the risk of creating brittle, hard-to-debug processes grows. The key to scalable and maintainable automation isn't to build monolithic systems, but to compose them from small, robust, and understandable components.
This is the core philosophy behind action.do—the fundamental building block of automation on the .do platform. An action.do represents a single, indivisible, and executable step. Think of it as a LEGO brick for your business logic. By designing these bricks effectively, you can construct powerful, resilient, and valuable Services-as-Software.
But what makes a "good" action? Let's dive into the best practices for designing action.do tasks that are simple, powerful, and ready for any workflow.
Before we get to specific practices, it's crucial to understand the design philosophy. A well-crafted action.do adheres to three core principles.
An atomic action is the smallest indivisible unit of work in a system. It performs a single, specific task.
Why is this so important? Atomicity makes your actions reliable, easily testable in isolation, and simple to debug. When a failure occurs, you know exactly which single step failed, not an ambiguous point within a complex function.
By design, actions are stateless. They receive input, perform their task, and produce an output without retaining any memory of previous executions. Think of them as pure functions: given the same input, they will always attempt the same operation.
This stateless nature is a feature, not a limitation. It ensures that actions are entirely predictable and reusable across countless different workflows. State management is handled at a higher level by the workflow.do orchestrator, which passes the necessary state into each action as input.
An action should have a well-defined "contract"—the inputs it expects and the output it produces. This makes them self-documenting and easy to compose.
Consider this example for sending a welcome email:
import { action } from '@do-sdk/core';
// Define an action to send a welcome email
const sendWelcomeEmail = action.create({
id: 'send-welcome-email',
description: 'Sends a welcome email to a new user.',
execute: async ({ email, name }) => {
// Your email sending logic via an external API
console.log(`Sending welcome email to ${name} at ${email}...`);
// A successful execution returns a structured object
return { success: true, messageId: 'xyz-123' };
}
});
The contract is clear: it requires an email and a name, and upon success, it returns an object containing a success flag and a messageId.
With those principles in mind, here are four practical tips for building effective actions.
An action should do exactly one thing. If you find yourself writing an action that "does X and then does Y," stop. You likely have two actions that should be chained together in a workflow.do.
Example: Instead of one large processSignup action, break it down:
This decomposition is the essence of building agentic workflows. Your workflow.do becomes the orchestrator that calls these simple, focused actions in sequence, handling the flow of data between them. This approach makes your automation easier to read, modify, and extend.
Avoid hardcoding values like API keys, URLs, or template IDs directly inside your execute function. This makes your action brittle and difficult to reuse. Instead, pass configuration in as properties.
Less Reusable:
// ...
execute: async ({ email, name }) => {
const apiKey = 'HARDCODED_API_KEY'; // Bad practice
// ... send email logic
}
// ...
More Reusable:
// ...
execute: async ({ email, name, apiKey }) => {
// Use the apiKey passed as an input
// ... send email logic
}
// ...
By passing configuration as input, you can reuse the same send-email action for transactional emails, marketing blasts, or password resets, simply by providing different inputs from the parent workflow.
What happens when a third-party API is down or a database write fails? Your action shouldn't silently fail or return an ambiguous result. It should throw a descriptive error.
// ...
execute: async ({ userId }) => {
try {
const response = await api.updateUser(userId);
if (!response.ok) {
throw new Error(`API failed with status ${response.status}`);
}
return { success: true };
} catch (error) {
console.error('Failed to update user:', error);
// Re-throw the error so the workflow can handle it
throw error;
}
}
// ...
Throwing an error allows the workflow.do orchestrator to catch it and decide on the next step—whether to retry the action, trigger a notification for a human, or branch to an alternative logic path.
The id and description fields in action.create are more than just comments. They are crucial metadata that makes your actions discoverable and understandable.
This metadata powers platform UIs, automated documentation, and helps other developers (including your future self) understand what an action does at a glance without having to read its source code.
By following these best practices, you create a library of robust, reusable, and atomic actions. These actions are the building blocks you use to compose complex workflow.do automations that represent complete business outcomes.
An onboard-new-user workflow.do is no longer a scary, monolithic script. It's a clear, declarative sequence of proven actions:
This is the future of automation: composing simple, powerful actions to deliver complex and valuable Services-as-Software. Start designing your actions with these principles, and you'll build workflows that are not only powerful but also resilient, scalable, and a pleasure to maintain.
Q: What is an 'atomic action' in the context of .do?
A: An atomic action is the smallest, indivisible unit of work in a workflow. It performs a single, specific task, like 'send an email' or 'update a database record', ensuring that operations are reliable, testable, and easy to debug.
Q: How does an action.do differ from a workflow.do?
A: An action.do represents a single step, while a workflow.do orchestrates multiple actions to achieve a larger business outcome. You build powerful workflows by composing a series of simple actions.
Q: Can I create my own custom actions?
A: Yes. The .do platform is designed for extensibility. You can define your own custom actions using our SDK, encapsulating your specific business logic and integrating any third-party API to make them available in any workflow.