API integrations are the backbone of modern software, but let's be honest: they can be a source of constant friction. Every time you need to connect to a new service like Stripe, Twilio, or Slack, you're faced with a familiar, tedious cycle. You hunt down API keys, write boilerplate code to handle authentication and requests, figure out error handling, and repeat this process across different microservices. The result? Scattered credentials, duplicated effort, and a brittle system that's hard to monitor and maintain.
What if you could build an integration once, wrap it in a secure and resilient shell, and make it available as a standardized, reusable service for your entire organization?
This is the core promise of action.do. By transforming a single API call into a managed atomic action, you can productize your business logic—turning messy integrations into clean, scalable building blocks for powerful workflow automation.
In this tutorial, we'll walk you through a step-by-step process of wrapping the Twilio API into a custom, secure, and reusable action.do that anyone on your team can use in minutes.
Before we dive into the solution, let's look closer at the common pain points of direct API integrations:
This ad-hoc approach simply doesn't scale. It creates technical debt and prevents you from building truly robust, agentic workflows.
action.do reframes the problem. Instead of thinking of an API call as a simple line of code, we treat it as a managed service—an atomic action.
An atomic action is the smallest, indivisible unit of work on the .do platform. When you create a custom action, you get a host of powerful features out of the box:
This is Business-as-Code in practice. Let's build it.
We'll create a new action called twilio.sendSms that takes to and body as inputs and sends an SMS message.
First, log in to your .do dashboard.
Now for the core logic. action.do allows you to run code in a secure sandboxed environment. Here's what a simple Node.js implementation looks like. You'd place this code in the editor provided in the .do UI.
// The code for your 'twilio.sendSms' action
const twilio = require('twilio');
/**
* Executes the action to send an SMS via Twilio.
* @param {object} payload - The action's payload.
* @param {object} payload.inputs - The inputs defined in your schema.
* @param {string} payload.inputs.to - The recipient's phone number.
* @param {string} payload.inputs.body - The message text.
* @param {object} context - The execution context, containing secrets.
* @param {object} context.secrets - Securely stored secrets for this action.
* @returns {Promise<object>} - A promise that resolves with the output.
*/
module.exports = async function({ inputs, context }) {
// 1. Retrieve secrets securely from the .do context
const accountSid = context.secrets.TWILIO_ACCOUNT_SID;
const authToken = context.secrets.TWILIO_AUTH_TOKEN;
const fromNumber = context.secrets.TWILIO_FROM_NUMBER;
// 2. Initialize the client
const client = twilio(accountSid, authToken);
// 3. Perform the task
const message = await client.messages.create({
to: inputs.to,
from: fromNumber,
body: inputs.body,
});
// 4. Return a structured output
console.log(`Message sent successfully. SID: ${message.sid}`);
return {
success: true,
messageSid: message.sid
};
}
Notice how clean this is. The logic is focused entirely on the task execution. Authentication and error handling are managed by the platform.
Your code needs API keys to run, but they don't belong in the code itself.
Click Deploy.
Your action is now live! The .do platform provides a simple UI where you can test the action directly. Enter a test phone number and message, and click Run. You can view the logs and the structured output ({ "success": true, "messageSid": "SM..." }) right in the dashboard.
You've successfully built a managed integration. Now, let's see how easy it is for any developer in your organization to use it.
Instead of wrestling with the Twilio SDK, they just need the .do SDK. Here’s how they would use your new action in a new user onboarding workflow:
import { Do } from '@do-sdk/core';
// Initialize the .do client with your API key
const doClient = new Do({ apiKey: 'YOUR_API_KEY' });
// Onboard a new user and send a welcome SMS
async function sendWelcomeSms(phone: string, name: string) {
try {
const result = await doClient.action('twilio.sendSms').run({
to: phone,
body: `Hi ${name}, welcome aboard! We're thrilled to have you.`
});
console.log('Welcome SMS Sent! Message SID:', result.messageSid);
return result;
} catch (error) {
// The .do platform handles retries automatically based on your config.
// This catch block is for terminal failures or for your own app's logic.
console.error('Failed to send welcome SMS after retries:', error);
}
}
// Trigger the action for a new user
sendWelcomeSms('+15551234567', 'Alex');
That's it. The code is clean, declarative, and secure. Your application developer doesn't need a Twilio key or even knowledge of the Twilio SDK. They just need to know the name of the action and its inputs. You've successfully built a piece of reusable, managed Business-as-Code.
What is an 'atomic action' on the .do platform?
An atomic action is the smallest, indivisible unit of work. It performs a single, specific task—like sending an email or updating a database record—ensuring each step in your workflow is reliable, testable, and independently executable.
How is action.do different from a regular function call?
action.do elevates a function to a managed service. Every execution is logged, monitored, secured, and scalable. It provides built-in retry logic, idempotency, and versioning, making it perfect for building resilient, distributed systems as Business-as-Code.
Can I define my own custom actions?
Absolutely. The .do platform empowers you to wrap your own code, scripts, or third-party API calls into a custom action. This transforms your unique business logic into a standardized, reusable, and callable Service-as-Software.
What happens if an action fails during execution?
The .do platform has built-in resilience. You can configure automatic retry policies with exponential backoff, define custom error handling logic, or trigger compensatory actions to ensure your workflows can gracefully handle failures without manual intervention.