Real Automation Hacks Freelancers Use to Keep Teams Moving

Real Automation Hacks Freelancers Use to Keep Teams Moving

1. What actually happens when you connect Calendly to Notion

So I’d rigged up this Calendly → Zapier → Notion pipeline for a client who wanted booked calls to show up on their shared Notion calendar. Easy, right? Except the event times came in as UTC, Notion displayed them in the editor’s time zone, and the team was half in Sydney, half in Berlin. I learned this the hard way when a PM joined a kickoff call twelve hours late, thinking he was early.

The real problem is that Calendly’s Zapier trigger delivers timestamp data in UTC without any offset hint, and Notion doesn’t normalize anything unless you pass in ISO 8601 with a Z or offset string at the end — and even then Notion sometimes re-interprets it based on the session’s timezone context.

You can force explicit time zone corrections with a Formatter step right in Zapier. Use Date/Time → Format and set the “To Timezone” manually (e.g., “Australia/Sydney”). Then convert again for whoever needs it from there. But here’s what I missed: if you use a dynamic timezone value from a prior field (say, based on invitee location), Formatter blows up quietly. It doesn’t error. It just returns a timestamp that’s wrong by hours and looks fine at a glance.

“The event showed up right on the calendar but three hours off. Nobody noticed until it was already over.”

2. Why Slack bot messages keep duplicating inside shared DMs

I built a Slack alert system using Zapier to notify freelancers about incoming client tickets. After lots of fiddling, it worked fine in channels but kept double-posting in DMs when the Zap used the “Send Direct Message” action. Stack traces showed nothing interesting, webhook logs were clean, retry attempts didn’t trigger. Felt like a Slack bug… but it wasn’t.

Turns out that if you’re sending to a DM thread you previously used with a bot, and Zapier is using an OAuth token from the workspace owner (not the user who originally created the DM), Slack sometimes routes it twice — one to the DM, one via the bot’s App Home message stream. This only happens when the bot sent a message to that user less than thirty minutes ago. Fun, right?

My fix (very unofficial): switch the action to “Custom Request” using Slack’s POST chat.postMessage method and set as_user to false. Then explicitly define the channel ID using the im.list API to pull the right one by user ID. It’s annoying, but consistent.

{
  "channel": "D123456",
  "text": "New ticket received:",
  "as_user": false
}

3. The Airtable field type that breaks webhook filtering

If you trigger automations based on Airtable record updates, here’s a gotcha that wasted an afternoon: using a formula field as your filter condition will sometimes prevent the Airtable webhook from firing at all — even though everything looks right in the UI.

In my case, I had a simple formula: IF(Status = "Ready", "Go", ""). The automation was set to trigger “when ‘Formula Field’ is ‘Go.’” But nothing happened when the status changed. Not delayed — never triggered. Even when I forced a re-sync.

The issue? Formula field updates don’t always register as “real changes” unless a non-derived field also updates in the same save cycle. And Airtable doesn’t surface this anywhere. It just… doesn’t count. Found this buried in a community post with like six replies from 2021.

  • If you’re using formulas in trigger filters, pair them with a dummy checkbox update on status change
  • Use scripting automations instead of the built-in filters if you need reliability
  • Timestamp field updates always trigger cleanly; use one as a control field
  • Airtable webhooks read new data but don’t re-evaluate formulas until the sheet recalculates
  • Don’t rely on computed fields alone for Zapier or Make triggers

Once I added a “Modified” checkbox toggled by a separate automation, everything lit up instantly.

4. Trying to update a Google Sheet row from multiple Zaps

I’ve been here too many times: two separate Zaps need to update the same Google Sheet. One writes new rows from a Typeform, another later fills in pricing data once someone reviews it. Sounds harmless. But then fields overwrite each other silently — even when you use Row ID matching.

Zapier doesn’t queue sheet writes, and Google Sheets doesn’t lock rows on write (there’s no transactional state). So if two Zaps hit the same row within a half-second — even if they touch different columns — the second one can overwrite the entire row with old field values.

Here’s what finally worked:

  • Insert a column called “Last Updated By” that logs the Zap title or scenario ID
  • Before writing, run a Get Row step to fetch the whole current row
  • Use a Formatter step to merge new values into the current row’s JSON via text functions
  • Then pass the full updated row back to the Update Row action — don’t individualize the fields

It’s dirty and brittle as hell, but it’s the only way I’ve found to prevent phantom overwrites when two Zaps race the update. Apparently Make has fewer issues here thanks to better transactional support, but Sheets API is still its own mess.

5. When Notion automations randomly stop working on shared pages

Notion’s Zapier triggers work decently — until you try automating changes across pages owned by different users in the same workspace. I set up a system where certain team members filled in metadata about weekly reports in a shared database. The Zap then tagged each row with a status and sent a Slack note to the reviewer. It worked…

Until a new team member duplicated the template report page. Their copy was technically “in” the same database visually, but didn’t trigger the automation. No errors in Zapier. No signs of a problem in the share settings. It was like the new records were a different instance.

Eventually tracked it down: the duplicated database was a snapshot, not a linked clone. Even though Notion reused the schema, it changed the internal page and block IDs. So the Zap’s trigger — “New Database Item in [X]” — didn’t catch the new entries. They were in [Y]. Which looks exactly the same.

Solution: never let users duplicate top-level pages that contain databases. If someone needs a reusable template, build a synced block or have them use a database template from inside the actual database view. Test the trigger with newly added content to verify ID match.

6. Automating multi-step approvals on Slack using only emoji reactions

I needed to build a lightweight approval chain in Slack for some freelance content workflows. Nothing heavy — just a way for three stakeholders to sequentially review posts and mark their stage. No one wanted to click links or log into anything. Just use Slack, some emojis, and natural flow.

So I went full caveman: Zapier watches a specific channel thread. When someone reacts with ✅, it timestamps Step 1 approved. Then it waits. When the same message gets a 📣 emoji from anyone named “Lisa” (yes, it was hardcoded), step two passes. If a 👀 is added after that, it triggers a webhook to publish the post.

It worked shockingly well — until someone removed a previous emoji before adding theirs. That broke the sequence. The thread lost state, and the Zap re-triggered from the top. And surprise, Slack doesn’t expose emoji removal events to Zapier.

I fixed it by generating a custom hash per thread using the message timestamp and storing status in a hidden Airtable table keyed to that hash. Each new reaction updated the stage manually based on existing flags.

{
  "thread_ts": "16728983.38392",
  "step_1": "✅",
  "step_2": "📣",
  "step_3": "👀"
}

The buried edge case: Slack treats emoji reactions from the mobile app slightly differently. On mobile, the emoji sometimes comes through a second or two later with a hydrated user field. If you’re filtering approvals by user role, you’ll miss these unless you wait three seconds before executing.

Of course I only discovered that after someone approved a $3K campaign from their iPhone and it didn’t go through.