Step 4: Add Executor
This step adds event-driven automation to your project management application using the Executor service. You'll create an executor that automatically sends Slack notifications whenever a new task is created, demonstrating how to build reactive systems that respond to database events.
What This Step Does
In this step, you enhance your application by:
- Adding executor configuration to your tailor.config.ts
- Creating a "new-task-slack-notification" executor
- Configuring a record created trigger that fires when a Task is created
- Implementing a webhook operation that sends task details to Slack
- Using the new record data to construct a notification message
Executors enable event-driven architecture by automatically responding to database events, scheduled triggers, or webhook calls. This allows you to build reactive systems that perform actions without manual intervention, such as sending notifications, updating related records, or calling external APIs.
Configuration Files
To follow along, review the configuration files for this step here.
tailor.config.ts
The configuration now includes executor:
<span><span style="color: var(--shiki-token-keyword)">import</span><span style="color: var(--shiki-color-text)"> { defineAuth</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> defineConfig</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> defineGenerators</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> } </span><span style="color: var(--shiki-token-keyword)">from</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"@tailor-platform/tailor-sdk"</span><span style="color: var(--shiki-color-text)">;</span></span>
<span><span style="color: var(--shiki-token-keyword)">import</span><span style="color: var(--shiki-color-text)"> { user } </span><span style="color: var(--shiki-token-keyword)">from</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"./src/db/user"</span><span style="color: var(--shiki-color-text)">;</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (</span><span style="color: var(--shiki-token-keyword)">!</span><span style="color: var(--shiki-token-constant)">process</span><span style="color: var(--shiki-color-text)">.</span><span style="color: var(--shiki-token-constant)">env</span><span style="color: var(--shiki-color-text)">.</span><span style="color: var(--shiki-token-constant)">WORKSPACE_ID</span><span style="color: var(--shiki-color-text)">) {</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">throw</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">new</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">Error</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">"WORKSPACE_ID environment variable is not set"</span><span style="color: var(--shiki-color-text)">);</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">export</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">default</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">defineConfig</span><span style="color: var(--shiki-color-text)">({</span></span>
<span><span style="color: var(--shiki-color-text)"> workspaceId</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">process</span><span style="color: var(--shiki-color-text)">.</span><span style="color: var(--shiki-token-constant)">env</span><span style="color: var(--shiki-color-text)">.</span><span style="color: var(--shiki-token-constant)">WORKSPACE_ID</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> name</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"project-management"</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> db</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> { </span><span style="color: var(--shiki-token-string-expression)">"main-db"</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> { files</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> [</span><span style="color: var(--shiki-token-string-expression)">`./src/db/*.ts`</span><span style="color: var(--shiki-color-text)">] } }</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> auth</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">defineAuth</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">"main-auth"</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> userProfile</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> type</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> user</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> usernameField</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"email"</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> attributes</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> role</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">true</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> machineUsers</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> manager</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> attributes</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> { role</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"MANAGER"</span><span style="color: var(--shiki-color-text)"> }</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> staff</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> attributes</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> { role</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"STAFF"</span><span style="color: var(--shiki-color-text)"> }</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> admin</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> attributes</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> { role</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"ADMIN"</span><span style="color: var(--shiki-color-text)"> }</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> })</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> pipeline</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> { </span><span style="color: var(--shiki-token-string-expression)">"main-pipeline"</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> { files</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> [</span><span style="color: var(--shiki-token-string-expression)">`./src/pipeline/*.ts`</span><span style="color: var(--shiki-color-text)">] } }</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> executor</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> { files</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> [</span><span style="color: var(--shiki-token-string-expression)">"./src/executor/*.ts"</span><span style="color: var(--shiki-color-text)">] }</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)">});</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">export</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">generators</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">defineGenerators</span><span style="color: var(--shiki-color-text)">(</span></span>
<span><span style="color: var(--shiki-color-text)"> [</span><span style="color: var(--shiki-token-string-expression)">"@tailor/kysely-type"</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> { distPath</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">`./src/generated/tailordb.ts`</span><span style="color: var(--shiki-color-text)"> }</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)">]</span></span>
<span><span style="color: var(--shiki-color-text)">);</span></span>
<span></span>
Key addition:
executor: Configures the Executor service to load executor definitions from./src/executor/
Other Files
All other files (database types, permissions, pipeline resolver) remain unchanged from Step 3. The executor integrates with the existing schema without requiring modifications.
src/executor/newTaskSlackNotification.ts
This file defines the executor that sends Slack notifications:
<span><span style="color: var(--shiki-token-keyword)">import</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> createExecutor</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> recordCreatedTrigger</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)">} </span><span style="color: var(--shiki-token-keyword)">from</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"@tailor-platform/tailor-sdk"</span><span style="color: var(--shiki-color-text)">;</span></span>
<span><span style="color: var(--shiki-token-keyword)">import</span><span style="color: var(--shiki-color-text)"> { task } </span><span style="color: var(--shiki-token-keyword)">from</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"../db/task"</span><span style="color: var(--shiki-color-text)">;</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">export</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">default</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">createExecutor</span><span style="color: var(--shiki-color-text)">(</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"new-task-slack-notification"</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)">)</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.on</span><span style="color: var(--shiki-color-text)">(</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">recordCreatedTrigger</span><span style="color: var(--shiki-color-text)">(task)</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> )</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">.executeWebhook</span><span style="color: var(--shiki-color-text)">({</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">url</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> ({ newRecord }) </span><span style="color: var(--shiki-token-keyword)">=></span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"https://hooks.slack.com/services/yourSlackWebhookURL"</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> headers</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"Content-Type"</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"application/json"</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">body</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> ({ newRecord }) </span><span style="color: var(--shiki-token-keyword)">=></span><span style="color: var(--shiki-color-text)"> ({</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"text"</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"New Task created :tada: "</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">+</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">newRecord</span><span style="color: var(--shiki-color-text)">.name</span></span>
<span><span style="color: var(--shiki-color-text)"> })</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> });</span></span>
<span></span>
This executor definition has three main parts:
-
Executor creation:
createExecutor("new-task-slack-notification")creates a new executor with a unique name -
Trigger configuration:
.on(recordCreatedTrigger(task))sets up the triggerrecordCreatedTrigger: Fires when a new record is createdtask: The type to monitor for new records- The trigger provides
newRecordcontaining the created task data
-
Action configuration:
.executeWebhook({...})defines what happens when triggeredurl: The Slack webhook URL (you'll need to replace this with your actual webhook)headers: HTTP headers for the requestbody: The payload sent to Slack, constructed from the new task data
Context Data
Triggers provide context data that can be used in actions:
newRecord: The newly created record (for recordCreatedTrigger)oldRecord: The record before update (for recordUpdatedTrigger)newRecord: The record after update (for recordUpdatedTrigger)record: The deleted record (for recordDeletedTrigger)
This data is available in the action configuration through destructuring:
<span><span style="color: var(--shiki-color-text)">body</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> ({ newRecord }) </span><span style="color: var(--shiki-token-keyword)">=></span><span style="color: var(--shiki-color-text)"> ({</span></span>
<span><span style="color: var(--shiki-color-text)"> text</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">`Task "</span><span style="color: var(--shiki-token-keyword)">${</span><span style="color: var(--shiki-token-constant)">newRecord</span><span style="color: var(--shiki-color-text)">.name</span><span style="color: var(--shiki-token-keyword)">}</span><span style="color: var(--shiki-token-string-expression)">" is due on </span><span style="color: var(--shiki-token-keyword)">${</span><span style="color: var(--shiki-token-constant)">newRecord</span><span style="color: var(--shiki-color-text)">.dueDate</span><span style="color: var(--shiki-token-keyword)">}</span><span style="color: var(--shiki-token-string-expression)">`</span></span>
<span><span style="color: var(--shiki-color-text)">})</span></span>
<span></span>
Expected Outcome
After deploying this configuration with npm run deploy, you should see:
- An executor "new-task-slack-notification" registered in your workspace
- The executor automatically triggers when new tasks are created
- Slack notifications sent to your configured channel
You can verify the setup by:
-
Creating a test task:
<span><span style="color: var(--shiki-token-keyword)">mutation</span><span style="color: var(--shiki-color-text)"> {</span></span> <span><span style="color: var(--shiki-color-text)"> createTask(input: {</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">name</span><span style="color: var(--shiki-color-text)">: </span><span style="color: var(--shiki-token-string-expression)">"Test notification"</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">description</span><span style="color: var(--shiki-color-text)">: </span><span style="color: var(--shiki-token-string-expression)">"Testing executor"</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">projectId</span><span style="color: var(--shiki-color-text)">: </span><span style="color: var(--shiki-token-string-expression)">"your-project-id"</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">status</span><span style="color: var(--shiki-color-text)">: TODO</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">dueDate</span><span style="color: var(--shiki-color-text)">: </span><span style="color: var(--shiki-token-string-expression)">"2025-12-31"</span></span> <span><span style="color: var(--shiki-color-text)"> }) {</span></span> <span><span style="color: var(--shiki-color-text)"> id</span></span> <span><span style="color: var(--shiki-color-text)"> name</span></span> <span><span style="color: var(--shiki-color-text)"> }</span></span> <span><span style="color: var(--shiki-color-text)">}</span></span> <span></span> -
Checking Slack:
- Verify that a notification appears in your Slack channel
- The message should say "New Task created :tada: Test notification"
-
Viewing executor logs:
- Check the Tailor Console for executor execution logs
- Verify that the executor ran successfully
- Review any errors if the notification didn't appear
Troubleshooting
If notifications aren't appearing:
- Check the webhook URL: Ensure you've replaced the placeholder with your actual Slack webhook URL
- Verify Slack app permissions: Make sure your Slack app has permission to post to the channel
- Review executor logs: Check the Tailor Console for error messages
- Test the webhook directly: Use curl to test the webhook URL:
<span><span style="color: var(--shiki-token-function)">curl</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">-X</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">POST</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">https://hooks.slack.com/services/YOUR_WEBHOOK_URL</span><span style="color: var(--shiki-color-text)"> \</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">-H</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"Content-Type: application/json"</span><span style="color: var(--shiki-color-text)"> \</span></span> <span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">-d</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'{"text": "Test message"}'</span></span> <span></span>
What You've Accomplished
By completing this step, you've built a complete event-driven system that:
- Automatically responds to database events
- Integrates with external services (Slack)
- Provides real-time notifications to your team
- Requires no manual intervention
Next Steps
Now that you've completed all four steps, you have a fully functional project management application with:
- Database schema with types and relationships
- Authentication with user profiles and machine users
- Role-based permissions at database and API layers
- Custom business logic with Pipeline resolvers
- Event-driven automation with Executors
Refer back to the overview page for suggestions on how to extend your application further.