How I Built a Human-in-the-Loop Social Media Bot With Drupal and Activepieces

Hi everyone,

I’m excited to share that I will be presenting at DrupalCon Nara about my learnings from integrating AI into marketing teams. I will be sharing a lot of interesting stuff from my slides, but to make the session more impactful, I wanted to talk about how easy it is to automate workflows.

These days, we have great no-code and low-code workflow tools like n8n, Zapier, and Activepieces.

I decided to go with Activepieces for this demo because it matches the Drupal energy, it is truly open-source. Dries Buytaert also talked about this in his DrupalCon Vienna keynote, so I was excited to try it.

The Problem: Why Build a “Bot”?

In this article, I will describe how you can build a simple bot to solve the infamous problem that comes after you publish your content on your website.

To publicize your new article, you then have to work on creating content for different social media channels, like LinkedIn, Twitter, and Mailchimp. This is manual work. It’s boring, and it takes time.

This is a perfect job for a bot! We can easily automate this with a workflow tool (like Activepieces for our demo).

But we have a new problem. We don’t want a “stupid” bot posting just anything to our company’s social media. We need a “smart” bot. We need a “human-in-the-loop” a real person who must approve the content before it goes live.

This automation leaves time for the marketing team to do the important work (reviewing and approving) and lets the bot do the boring work (posting).

So, Let’s Get Started

Let’s go step by step. Here is how we built our smart bot.

Step 1: Set up the “Ear” (The Webhook at the Drupal Site)

This is the starting point. Our bot needs an “ear” to listen for new posts. We use the standard Webhooks module in Drupal for this.

We set up a new webhook that triggers after a new “Blog Post” is saved and published. We configure this webhook to send all the article data (like the title and the full HTML body) to a special URL that Activepieces will give us in the next step.

Step 2: Build the “Brain” (The Flow in Activepieces)

This is where all the logic happens. Here is the exact, step-by-step guide to build the bot’s brain.

1. Trigger: Webhook

  • What it does: This is the first step in your flow. It gets the URL from Step 1. It just waits to “hear” the data from Drupal.
  • Pro-Tip: Click “Test trigger” in Activepieces and then publish a test post in Drupal. This will pull in a sample of your data, which makes the next steps much easier.

2. Clean the Data

  • Piece: Text Helper
  • Action: Remove HTML
  • Why: The data from Drupal is in HTML (like <p>, <h2>, etc.). Our bot’s brain (the AI) needs clean text. This step “cleans” the article.
  • Input: In the “Text” field, map the body.entity.body[0].value data from Step 1.

3. Ask the AI Assistant for Ideas

  • Piece: OpenAI
  • Action: Ask GPT-4o (or any model you like)
  • Why: This is our AI assistant. We give it the clean text and ask it to write our social media posts. The most important part is telling it to return the answer in JSON format so the bot can read it.
  • Prompt: Use a prompt like this.

    You are an expert social media copywriter. Your only job is to return a valid, minified JSON object. Do not add any text before or after the JSON.

    Based on the following article: [Drag the 'text' output from Step 2 here]

    Your JSON output must follow this structure:

    {
      "linkedinPost": "Your professional LinkedIn summary here.",
      "twitterPost": "Your engaging, punchy tweet here with hashtags."
    }
    

4. Understand the AI’s Answer

  • Piece: Code
  • Why: The AI (Step 3) will sometimes wrap its JSON answer in text like ` ``json … ``` `. This will cause an error. We use a small code step to safely extract only the clean JSON.
  • Input: Create an input called raw_text and map the AI’s response (from Step 3) to it.
  • Code: Paste this code into the code box.
    export const code = async (inputs) => {
      const rawText = inputs.raw_text || "";
      // This regex finds the JSON between the ```json tags
      const jsonMatch = rawText.match(/```json\n([\sS]*)\n```/);
      const jsonString = jsonMatch ? jsonMatch[1] : rawText;
    
      try {
        return JSON.parse(jsonString);
      } catch (e) {
        return { error: "Failed to parse JSON from AI" };
      }
    };
    
  • The output of this step will be two clean data pills: linkedinPost and twitterPost.

5. Build the “Human-in-the-Loop” Gate

  • Piece: Slack
  • Action: Request Approval in a Channel
  • Why: This is the most important step of our smart bot! It pauses the flow and sends a message to a human for review.
  • Message: In the “Message” field, you can now use the data from Step 4.

    New post for approval:

    LinkedIn: [Drag the 'linkedinPost' output from Step 4] Twitter: [Drag the 'twitterPost' output from Step 4]

6. The Bot’s Decision-Making

  • Piece: Route
  • Why: This step checks what button the human clicked in Slack.
  • Route 1: Add a route and set the condition:
    • First Value: approve
    • Condition: (Text) Is
    • Second Value: true
  • The “Default” path will be our “Reject” path, where the bot does nothing.

7. The Bot Does the Boring Work

  • Inside “Route 1”, add the steps for the bot to do its job:
  • Piece: LinkedIn
    • Action: Create Company Update
    • Text: [Drag the 'linkedinPost' output from Step 4]
  • Piece: Twitter
    • Action: Post Tweet
    • Text: [Drag the 'twitterPost' output from Step 4]
  • You can also add a Slack step here to send a “✅ Success!” message.

The Final Demo

I will run this demo live on stage at DrupalCon Nara.

Let’s see if it works!!