WorkflowsMCP

Content pipeline: Notion calendar → social

Editorial calendar nudges the owner in Slack, then schedules the post in Buffer.

@workflowsmcpFeb 11, 2026Verified Aug 2026contentmarketingschedulingeditorial-calendar

A content calendar only works if it chases people. This workflow watches a Notion editorial calendar, DMs the owner 48 hours before publish with the draft copy and an approve button, and on approval pushes the post into Buffer at the scheduled time — then writes the Buffer permalink back onto the Notion row so the calendar stays the single source of truth.

How it flows

  1. 01

    Hourly sweep of the calendar

    Rows that are Ready and publishing within 48 hours are picked up.

  2. 02

    Owner DMed with the draft copy

    Full text inline plus Approve and Edit buttons.

  3. 03

    Approval captured

    Slack posts the click to your interactivity Request URL; the handler acknowledges inside 3 seconds, then works. The button carries the Notion page id, so it needs no lookup table.

  4. 04

    Post created in Buffer

    One createPost per Buffer channel matching the Channel multi-select, due at the exact Publish At time.

  5. 05

    Notion row updated to Scheduled

    Buffer permalink written back so the calendar reflects reality.

  6. 06

    Unapproved items escalate

    Still Ready at 12 hours out? The nudge repeats once, then pings the content lead.

Set up each app

Work through these in order — later apps usually need a token or an id from an earlier one.

Notion

The editorial calendar and approval state

  1. 01

    Build the "Editorial Calendar" database

    Properties: Title (Title), Publish At (Date, with time enabled), Channel (Multi-select: X, LinkedIn, Newsletter), Status (Status: Idea → Drafting → Ready → Scheduled → Published), Owner (Person), Copy (Text), Asset (File), Buffer URL (URL). Switch on "Include time" for Publish At or everything schedules at midnight. Status options always sit inside one of Notion's three groups, so put Idea and Drafting under To-do, Ready and Scheduled under In Progress, and Published under Complete — the group is what view filters and the API status group filter key off.

  2. 02

    Query the nudge window

    Share the database with your internal connection first (••• → Connections → Add connection) and keep both "read content" and "update content" on, or you get a 404 on a database you can plainly see and a 403 on the write-back. Retrieve the database once for its data source id: GET /v1/databases/{database_id} returns data_sources[].id. Then, hourly, POST /v1/data_sources/{data_source_id}/query with Notion-Version: 2026-03-11 (/v1/databases/{id}/query is deprecated) for rows that are Ready and publishing in the next 48 hours. Filter the raw Publish At date rather than a now() formula: the two bounds below are computed by your job, so nothing depends on when Notion last recalculated a formula.

    Data source query filter — now to now + 48h
    {
      "filter": {
        "and": [
          { "property": "Status", "status": { "equals": "Ready" } },
          { "property": "Publish At", "date": { "after": "2026-03-21T09:00:00Z" } },
          { "property": "Publish At", "date": { "on_or_before": "2026-03-23T09:00:00Z" } }
        ]
      },
      "sorts": [{ "property": "Publish At", "direction": "ascending" }]
    }

Slack

Nudges the owner and captures approval

  1. 01

    DM the owner, do not post to a channel

    Match the Notion Person property to a Slack user by email with users.lookupByEmail, then call conversations.open with that single user id and post to the channel.id it returns. Bot scopes: users:read.email for the lookup (plain users:read is not enough), im:write to open the DM, chat:write to send it. Notion's own Slack connection can DM an assignee, but only on an edit event — it has no "N hours before a date" trigger, which is why this runs on a schedule instead of using the native automation.

  2. 02

    Turn interactivity on and give Slack a Request URL

    api.slack.com/apps → your app → Interactivity & Shortcuts → toggle Interactivity on → set the Request URL. Skip this and the approve button is inert, which takes the whole workflow with it. Slack POSTs a form-encoded payload parameter that you parse as JSON, and it wants an acknowledgement within 3 seconds — acknowledge first, call Buffer after. The link button counts too: clicking "Edit in Notion" still delivers a payload you have to acknowledge.

  3. 03

    Send the copy with an approve action

    Include the full copy in the message so approval does not require opening Notion on a phone. The action value carries the Notion page id back to your handler. For the edit link, use the url the query already returned on each page object rather than composing one from the id — the composed form has changed before and the API hands you the current one for free.

    Approval message blocks
    {
      "blocks": [
        { "type": "section",
          "text": { "type": "mrkdwn", "text": "*Publishing Monday 09:00 — LinkedIn*\n\n> Six months of build logs, one honest retro. Here is what we would not do again." } },
        { "type": "actions",
          "elements": [
            { "type": "button", "style": "primary", "text": { "type": "plain_text", "text": "Approve & schedule" },
              "value": "notion:2f91c4a70b8d4e5f9a1c3b6d8e0f2a4b", "action_id": "content_approve" },
            { "type": "button", "text": { "type": "plain_text", "text": "Edit in Notion" },
              "url": "https://app.notion.com/p/Six-months-of-build-logs-2f91c4a70b8d4e5f9a1c3b6d8e0f2a4b" }
          ] }
      ]
    }

Buffer

Holds the scheduled queue per channel

  1. 01

    Point your agent at Buffer's MCP server, or take an API key

    Buffer ships an official MCP server alongside its public API, and for an agent that is the path: connect it and the scheduling below is a tool call instead of hand-rolled HTTP. Building it by hand instead? Same credential either way — publish.buffer.com/settings/api → Create API key, sent as an Authorization Bearer header against api.buffer.com, on every plan including Free. What you cannot do any more is the old route: new client creation on the legacy REST API is closed and that API retires 2027-02-01, so there is no v1 token left to generate.

  2. 02

    Map Buffer channels to your Notion Channel values

    One Buffer channel is one value of the Notion Channel multi-select. Fetch the list once and keep the mapping in config. The legacy profile_ids array is gone: createPost takes a single channelId, so a row tagged with three channels is three calls, not one.

    List the connected channels
    query {
      channels(input: { organizationId: "8f31c0a9e2b7" }) {
        id
        service
      }
    }
  3. 03

    Create one scheduled post per channel

    mode is an enum, and the failure to design against is addToQueue — it drops the post in the next free slot and desyncs the calendar. Pass customScheduled with dueAt as an ISO 8601 UTC timestamp taken straight from Publish At. assets is required, so send an empty array for a text-only post and put the link in text or in the network's metadata.

    createPost — one per channelId
    mutation {
      createPost(input: {
        channelId: "5f2b9c1a7e4d3b0016a2c9d1"
        text: "Six months of build logs, one honest retro."
        assets: []
        mode: customScheduled
        dueAt: "2026-03-23T09:00:00Z"
        schedulingType: automatic
      }) { post { id status dueAt } }
    }
  4. 04

    Write the permalink back to Notion

    On success, PATCH the Notion row: Status → Scheduled, Buffer URL → the link for the created post. This is the call that needs "update content" on the connection you shared the database with. Now the calendar shows what is actually queued, not what someone intended.