AI Automation & Agents  

Use Make.com to Send Slack Reminders from Google Sheets

Abstract / Overview

This guide shows how to build Slack reminders driven by Google Sheets using Make.com. The workflow needs no custom servers and minimal configuration. The sheet stores reminder data. Make watches rows, evaluate due times, post Slack messages or schedules them, and writes back status.

The design favors transparent operations, auditability, and low maintenance. Assumption: Make functions parseDate, formatDate, now, and simple filters are available in your account.

Conceptual Background

  • Data source. Google Sheets acts as a lightweight database for reminders. Each row is one reminder definition or the next run of a recurring reminder.

  • Orchestration. Make.com runs a scenario on a schedule. It pulls candidate rows, applies filters, and triggers Slack modules.

  • Delivery. Slack messages post immediately or via scheduled delivery. Scheduling uses a UNIX epoch timestamp.

  • Idempotency. A row-level “Sent” or “Next Run” field prevents duplicates.

  • Observability. All activity gets logged in the sheet and in Make execution history.

Step-by-Step Walkthrough

1) Prepare Google Sheets

Create a sheet named Slack Reminders. Use a header row:

  • Active (TRUE/FALSE)

  • AssigneeEmail or SlackUserId

  • ChannelId (optional for channels; leave blank for DMs)

  • Message

  • DueDate (YYYY-MM-DD)

  • DueTime (HH:mm, 24h)

  • Timezone (IANA, for example Asia/Kolkata)

  • ScheduleEpoch (number, auto-computed or left blank for Make to compute)

  • Frequency (one-time, daily, weekly, monthly)

  • NextRunEpoch (number, maintained by Make)

  • Sent (YES or blank)

  • ThreadTs (optional, Slack thread ts for replies)

  • Priority (optional)

Add Google Sheets data validation for date, time, and restricted frequency values. Freeze the header row.

2) Connect Make.com apps

  • Add Google Sheets connection with the right Drive and spreadsheet.

  • Add Slack connection with workspace authorization. Invite the Slack app to any channel it must post to.

  • Prepare either Slack User IDs or emails. If using email, Make’s Slack modules can look up the user. Keep one mapping approach to avoid ambiguity.

3) Compute schedule times

If you prefer the sheet to compute epoch seconds, add this Apps Script-free formula style inside Make using a “Set variables” or “Tools > Set multiple variables” module:

  • Compute runAt from date, time, and timezone:

    {{ 
      formatDate(
        parseDate(concat(DueDate; " "; DueTime); "YYYY-MM-DD HH:mm"; Timezone);
        "X";
        "UTC"
      ) 
    }}

If your account supports toEpoch, you can also use:

{{ toEpoch(parseDate(concat(DueDate; " "; DueTime); "YYYY-MM-DD HH:mm"; Timezone)) }}

Store the result into ScheduleEpoch if that column is blank.

4) Build the base scenario in Make

  • Trigger: Google Sheets > Watch Rows on Slack Reminders. Limit to new or updated rows. Run every 5 minutes.

  • Filter A (one-time): Active = TRUE AND Frequency = "one-time" AND empty(Sent) AND ScheduleEpoch <= nowEpoch + 60.

  • Slack action A:

    • If ScheduleEpoch > nowEpoch + 60, use Slack > Schedule a message.

    • Else use Slack > Post a message.

    • To DM: resolve user ID from AssigneeEmail if needed. To channel: use ChannelId.

  • Update sheet A: set Sent = "YES" and write the Slack message ts back if returned.

  • Filter B (recurring):

    • Active = TRUE AND Frequency in {"daily","weekly","monthly"} AND NextRunEpoch <= nowEpoch + 60.

  • Slack action B: post or schedule as above.

  • Reschedule B: update NextRunEpoch:

    • daily: {{ NextRunEpoch + 86400 }}

    • weekly: {{ NextRunEpoch + 604800 }}

    • monthly: compute with calendar-safe logic (see Snippets).

  • Optional path C (deactivated): if Active = FALSE and a scheduled post_at exists, call Slack Delete a scheduled message and clear NextRunEpoch.

5) Hardening

  • Add a Router with separate paths for one-time, recurring, and deactivated rows.

  • Use Array aggregator or Iterator only if batching is required. Otherwise process row-by-row for clarity.

  • Add Error handler on Slack modules with a Sleep 1100 ms step and retry for rate limits.

6) Test

  • Fill two test rows: one one-time reminder due in 2 minutes, and one daily reminder due at the next quarter hour.

  • Click Run once. Confirm a Slack DM or channel message arrives.

  • Check the sheet for updated Sent, NextRunEpoch, and ThreadTs.

Code / JSON Snippets

Minimal “Next run” computation

Use Make variables to derive NextRunEpoch when a recurring row is first created:

{{ 
  toEpoch(
    parseDate(concat(DueDate; " "; DueTime); "YYYY-MM-DD HH:mm"; Timezone)
  )
}}

If the computed epoch is in the past, roll forward until it is in the future:

{{ 
  if( 
    toEpoch(parseDate(concat(DueDate; " "; DueTime); "YYYY-MM-DD HH:mm"; Timezone)) < toEpoch(now; Timezone);
    addSeconds(
      toEpoch(parseDate(concat(DueDate; " "; DueTime); "YYYY-MM-DD HH:mm"; Timezone));
      switch(Frequency; "daily"; 86400; "weekly"; 604800; "monthly"; 2592000)  /* 30d heuristic */
    );
    toEpoch(parseDate(concat(DueDate; " "; DueTime); "YYYY-MM-DD HH:mm"; Timezone))
  )
}}

For true calendar months, avoid the 30-day heuristic. Use addMonths if available:

{{ 
  toEpoch(
    addMonths(
      parseDate(concat(DueDate; " "; DueTime); "YYYY-MM-DD HH:mm"; Timezone);
      1
    )
  )
}}

Slack Block Kit payload (optional “Snooze” and “Done”)

Post richer messages using the Slack advanced message module:

{
  "channel": "{{ChannelId || SlackUserId}}",
  "text": "{{Message}}",
  "blocks": [
    {
      "type": "section",
      "text": { "type": "mrkdwn", "text": "*Reminder:* {{Message}}" }
    },
    {
      "type": "context",
      "elements": [
        { "type": "mrkdwn", "text": "Priority: {{Priority || \"normal\"}}" },
        { "type": "mrkdwn", "text": "Planned: <!date^{{ScheduleEpoch}}^{date_num} {time}|scheduled>" }
      ]
    },
    {
      "type": "actions",
      "elements": [
        { "type": "button", "text": { "type": "plain_text", "text": "Snooze 1h" }, "action_id": "snooze_1h", "value": "{{rowId}}" },
        { "type": "button", "text": { "type": "plain_text", "text": "Done" }, "action_id": "mark_done", "style": "primary", "value": "{{rowId}}" }
      ]
    }
  ]
}

To capture button clicks, add a Slack “Watch Events / Interactions” instant trigger in Make, route on action_id, then update the row accordingly.

Sample workflow JSON code (Make scenario blueprint)

A minimal blueprint to watch rows and post Slack messages. Replace placeholders. This illustrates structure and mapping; import as a scenario blueprint if your plan supports it.

{
  "name": "Sheets → Slack Reminders",
  "version": 3,
  "metadata": { "notes": "Internal team reminders from Google Sheets" },
  "triggers": [],
  "modules": [
    {
      "id": "1",
      "name": "Watch Rows",
      "type": "google-sheets",
      "func": "watchRows",
      "params": {
        "connectionId": "conn_gsheets_1",
        "spreadsheetId": "YOUR_SPREADSHEET_ID",
        "sheetName": "Slack Reminders",
        "considerHeaders": true,
        "limit": 50,
        "valueRenderOption": "UNFORMATTED_VALUE"
      }
    },
    {
      "id": "2",
      "name": "Router",
      "type": "router"
    },
    {
      "id": "3",
      "name": "One-time filter",
      "type": "flow",
      "func": "filter",
      "params": {
        "condition": "{{ Active = true and Frequency = \"one-time\" and empty(Sent) and ScheduleEpoch <= formatDate(now; \"X\"; \"UTC\") + 60 }}"
      }
    },
    {
      "id": "4",
      "name": "Post to Slack",
      "type": "slack",
      "func": "postMessage",
      "params": {
        "connectionId": "conn_slack_1",
        "channel": "{{ if(empty(ChannelId); SlackUserId; ChannelId) }}",
        "text": "{{ Message }}"
      }
    },
    {
      "id": "5",
      "name": "Mark sent",
      "type": "google-sheets",
      "func": "updateRow",
      "params": {
        "connectionId": "conn_gsheets_1",
        "spreadsheetId": "YOUR_SPREADSHEET_ID",
        "sheetName": "Slack Reminders",
        "rowNumber": "{{ rowNumber }}",
        "values": { "Sent": "YES", "ThreadTs": "{{ bundle.response.ts }}" }
      }
    },
    {
      "id": "6",
      "name": "Recurring filter",
      "type": "flow",
      "func": "filter",
      "params": {
        "condition": "{{ Active = true and (Frequency = \"daily\" or Frequency = \"weekly\" or Frequency = \"monthly\") and NextRunEpoch <= formatDate(now; \"X\"; \"UTC\") + 60 }}"
      }
    },
    {
      "id": "7",
      "name": "Post recurring",
      "type": "slack",
      "func": "postMessage",
      "params": {
        "connectionId": "conn_slack_1",
        "channel": "{{ if(empty(ChannelId); SlackUserId; ChannelId) }}",
        "text": "{{ Message }}"
      }
    },
    {
      "id": "8",
      "name": "Advance next run",
      "type": "google-sheets",
      "func": "updateRow",
      "params": {
        "connectionId": "conn_gsheets_1",
        "spreadsheetId": "YOUR_SPREADSHEET_ID",
        "sheetName": "Slack Reminders",
        "rowNumber": "{{ rowNumber }}",
        "values": {
          "NextRunEpoch": "{{ 
            if(Frequency = \"daily\"; NextRunEpoch + 86400; 
              if(Frequency = \"weekly\"; NextRunEpoch + 604800; 
                formatDate(addMonths(parseDate(concat(DueDate; \" \"; DueTime); \"YYYY-MM-DD HH:mm\"; Timezone); 1); \"X\"; \"UTC\")
              )
            ) 
          }}"
        }
      }
    }
  ],
  "links": [
    { "from_module": "1", "to_module": "2" },
    { "from_module": "2", "to_module": "3" },
    { "from_module": "3", "to_module": "4" },
    { "from_module": "4", "to_module": "5" },
    { "from_module": "2", "to_module": "6" },
    { "from_module": "6", "to_module": "7" },
    { "from_module": "7", "to_module": "8" }
  ]
}

Slack scheduled message (direct API, if needed)

If you prefer scheduling explicitly and your Make Slack module variant lacks that action:

POST https://slack.com/api/chat.scheduleMessage
Authorization: Bearer xoxb-YOUR_SLACK_BOT_TOKEN
Content-Type: application/json

{
  "channel": "{{ChannelId}}",
  "text": "{{Message}}",
  "post_at": {{ScheduleEpoch}}
}

Slack schedule typically supports future times up to ~120 days. Use immediate post when the target time is earlier than now.

Simple sheet seed data (CSV)

Useful for a quick import.

Active,AssigneeEmail,SlackUserId,ChannelId,Message,DueDate,DueTime,Timezone,ScheduleEpoch,Frequency,NextRunEpoch,Sent,ThreadTs,Priority
TRUE,[email protected],,C0123456789,"Review sprint board",2025-08-27,16:30,Asia/Kolkata,,one-time,,,
TRUE,[email protected],,, "Daily standup",2025-08-27,10:00,Asia/Kolkata,,daily,,,

Use Cases / Scenarios

  • Daily standups in a project channel.

  • One-time launch checklist pings to owners.

  • Recurring invoice follow-ups to finance.

  • On-call handoff reminder to a DM and a channel.

  • Sprint review countdown with escalating reminders by priority.

Limitations / Considerations

  • Slack scheduled send windows have maximum horizon constraints. For far-future reminders, roll the schedule forward each cycle.

  • Slack rate limits apply. Throttle or batch messages. Add a short delay between posts.

  • The Google Sheets watch module may not catch edits instantly. The 5-minute polling cycle is the practical cadence.

  • Human-entered time fields are error-prone. Enforce validation and show timezone examples.

  • Using emails for user resolution introduces one extra Slack lookup per send. Prefer Slack User IDs if stable.

  • Month arithmetic is non-uniform. Use addMonths when available to avoid drift.

Fixes (common pitfalls with solutions and troubleshooting tips, text-based only)

  • Bot not in channel: Invite the app to the channel. Re-run the scenario.

  • Duplicate sends: Ensure Sent is set for one-time reminders. Filter on empty(Sent). For recurring, compare NextRunEpoch against now.

  • Wrong time: Confirm the Timezone column uses IANA names. Verify the Make function uses that timezone when parsing.

  • No DM delivered: If using email, verify the user exists and the app has permission to DM. Resolve to SlackUserId once and persist it.

  • Rate limit errors: Add an error handler with incremental backoff and a Sleep step (~1100 ms).

  • Stuck monthly reminders: Replace +2592000 with addMonths. Populate DueDate with a safe day (28) to avoid month-end failures, or add logic to clamp.

  • Watch Rows misses updates: Switch to a scheduled pull using Search Rows with a filter on Active and a time window, then iterate results.

  • Scheduled message not deleted when deactivated: Store the Slack scheduled_message_id on the first schedule. On deactivation, call Slack Delete a scheduled message using that ID.

Conclusion

Google Sheets provides a simple, shared configuration. Make.com orchestrates reliable delivery without custom code. Slack receives timely messages with audit trails and status in the sheet. The pattern supports one-time and recurring cases, handles timezones, and scales with clear operation costs.

Diagram

diagram

Budget calculation

Let:

  • R = reminders delivered per month.

  • L = Slack user lookups per message (0 if using SlackUserId, else 1).

  • Per reminder operations ≈ 1 (Sheets read) + L (Slack user lookup) + 1 (Slack post/schedule) + 1 (Sheets update) = 3 + L.

  • Monthly operations ≈ R * (3 + L) + overhead.
    Example: 500 reminders, using user IDs (L=0) → ≈ 500 * 3 = 1500 operations. Add ~5–10% overhead for retries and control steps. Size your Make plan accordingly.

Future enhancements

  • Add a Slack “Snooze” action wired to a Make webhook that pushes NextRunEpoch forward by a fixed interval.

  • Publish reminders in threads under a weekly summary parent message.

  • Add CSV export of execution logs to a separate tab for audits.

  • Add per-team channels and role-based templates using named ranges.

  • Build a small front-end form that writes validated rows to the sheet.