In a world driven by automation, our workflows can quickly become complex, monolithic, and difficult to manage. What if we could break down every business process into its smallest, most fundamental components? This is the core philosophy behind the .do platform: treating work as a series of defined, manageable, and reusable atomic actions.
Welcome to the concept of Business as Code. By defining discrete tasks as programmable building blocks, you can construct powerful, scalable, and transparent agentic workflows.
This hands-on tutorial will guide you through creating your very first atomic action using the .do SDK. We'll build a simple yet practical action to send a welcome email, demonstrating just how easy it is to define, execute, and scale individual tasks.
Before we dive into the code, let's clarify what we mean by an "atomic action." Think of it as the smallest, indivisible unit of work in any process. It's a self-contained, executable task designed to do one thing and do it well.
Actions are the fundamental building blocks. You compose these simple, reusable blocks to create sophisticated and robust workflows, much like using individual bricks to construct an entire building.
Let's get our hands dirty and build a real action. We'll use TypeScript to define an action that sends a welcome email to a new user.
First, create a new project directory and install the @do-co/agent package.
mkdir do-action-tutorial
cd do-action-tutorial
npm init -y
npm install @do-co/agent
Now, create a file named index.ts and add the following code. We'll break it down piece by piece.
import { Action } from '@do-co/agent';
// Define a new atomic action to send a welcome email
const sendWelcomeEmail = new Action('send-welcome-email', {
title: 'Send Welcome Email',
description: 'Sends a standardized welcome email to a new user.',
input: {
to: { type: 'string', required: true },
name: { type: 'string', required: true },
},
async handler({ to, name }) {
console.log(`Sending email to ${to}...`);
// Actual email sending logic (e.g., using an SMTP service) would go here
const message = `Welcome to the platform, ${name}!`;
console.log(message);
return { success: true, messageId: `msg_${Date.now()}` };
},
});
// Execute the action with specific inputs
async function runExample() {
console.log('--- Running the sendWelcomeEmail action ---');
const result = await sendWelcomeEmail.run({
to: 'new.user@example.com',
name: 'Alex',
});
console.log('--- Action Result ---');
console.log(result);
}
runExample();
Let's dissect this code:
new Action('send-welcome-email', { ... }): This is where we instantiate our action. The first argument, 'send-welcome-email', is a unique, machine-readable identifier for this action.
title & description: This metadata makes your action discoverable and understandable to other developers (and even business users) on the .do platform.
input Schema: This is a critical part. It defines the "data contract" for your action. We're explicitly stating that this action requires two string inputs: to and name. The platform can use this for validation, type-checking, and even generating user interfaces for your workflows.
async handler({ to, name }): This is the heart of your action—the logic that gets executed. Notice how the input parameters to and name are destructured directly. Your handler performs the single task and returns a structured result, confirming its success and providing a reference messageId.
The runExample function at the end shows how you can execute this atomic action. The .run() method takes an object that matches the input schema you defined.
To run this code, you'll first need to configure your project for TypeScript. Install the necessary dependencies:
npm install -D typescript ts-node
And then execute your file using ts-node:
npx ts-node index.ts
You should see the following output, confirming your action was defined and executed successfully:
--- Running the sendWelcomeEmail action ---
Sending email to new.user@example.com...
Welcome to the platform, Alex!
--- Action Result ---
{ success: true, messageId: 'msg_1678886400000' } // Timestamp will vary
Congratulations! You've just defined and executed your first atomic action.
You might be thinking, "This is just a function wrapper." But it's so much more. By defining your business logic this way, you unlock three powerful benefits:
You've successfully transformed a business process into a version-controlled, testable, and reusable piece of code—the very essence of task automation done right.
An atomic action is the smallest, indivisible unit of work in a workflow. It's a self-contained, executable task—like sending an email, querying a database, or calling an external API. Each action is designed to do one thing well.
Actions are the individual steps, while workflows are the orchestration of multiple actions in a specific sequence or logic. You build complex workflows by composing simple, reusable actions together.
Absolutely. The .do SDK allows you to define custom actions with specific inputs, outputs, and business logic. This transforms your unique business operations into reusable, programmable building blocks for any workflow.
Ready to move beyond monolithic scripts? Start building your agentic workflows with the fundamental building block of modern automation. Dive in and turn your operations into code with action.do.