How Freelancers Really Use Automation to Stay Sane Mid-Client Storms
1. Setting up fallback alerts for Zapier zap failures
I thought my Stripe-to-ClickUp client setup was stable. It wasn’t. One Thursday, while juggling three draft invoices and an inbox full of Loom links, I noticed a deliverable didn’t make it to ClickUp. Turns out the Zap failed two days earlier. No alert. No red mark. Just… silence.
Here’s the thing: Zapier’s default error handling isn’t what you think it is. Unless you’ve manually added email error notifications under Settings → Notifications, you’ll get nothing if a Zap breaks mid-run. If it quietly fails a filter? Nothing. If a webhook suddenly gets a 403 from a gated spreadsheet? You might catch it a day later in Task History — if you notice.
Quick sanity saver:
// Add this after any important Zapier action
Action: Send outbound webhook
URL: Discord channel or Slack Incoming Webhook
Payload: {"zap_triggered": true, "client": "{{client_name}}"}
This dummy webhook bypasses Zapier’s delays and delivers real-time proof that the Zap got past a certain point. It’s not elegant, but it’s saved me from phantom delivery failures more than once.
Also—was fun discovering that if a Zap fails due to a missing required field (e.g., ClickUp Task Title blank), and that data is coming from a dynamically formatted Formatter output field, it might just skip silently unless you explicitly ‘Require’ the field in ClickUp’s setup screen. That checkbox? Easy to overlook.
2. Using Notion databases to auto-fill onboarding emails
I use Notion like a long-term memory supplement. Every client’s start date, email preferences, and project taglines live inside a database I call ONBOARDS. Works wonderfully until I need that data programmatically. Notion’s API is… verbose. And inconsistent. Same property type? Different response objects depending on when it was filled, especially with multi-selects.
During a flow I built with Make, I hit a snag pulling a client’s preferred comms channel. Notion returned an empty array — while the UI clearly showed the value as “Slack.” Turns out if the field was edited before being added to the database template, the property ID changes behind the scenes.
Partial workaround I found: In Notion, delete the field entirely and re-add it clean with the same name. Yes, annoying. But it resets the internal schema object and stops the inconsistent returns from the GET call.
I now have a scenario that fetches client properties via Make, then pipes their preferred tool and name into a templated email in Gmail. It runs as a scenario triggered by a checkbox field called “SEND GMAIL ONBOARD.” That boolean still fires twice sometimes, though, which I haven’t fully debugged. My workaround is a 30-second time delay plus using a modified timestamp field in Notion to ensure a unique execution.
3. Building client-specific shortcuts in Alfred with dynamic prompts
If you don’t use Alfred with Powerpack as a freelancer, you’re working too hard. I started with basic stuff (open project folder, launch Figma file), but a few weeks ago I managed to wire it deeper. Now I run “clientdeck ACME” and it opens the proper Notion, ClickUp, and Slack channels in one go.
Here’s how I got it semi-dynamic:
- Client data lives in a CSV file inside Dropbox, called
clientmap.csv
- Each row contains client code, Notion URL, ClickUp space ID, Slack team name
- Alfred calls a bash script that reads the CSV, parses the row for the input key
- It then launches each URL with
open
orxdg-open
depending on macOS/Linux
The script broke once when I added a comma inside a Notion title (“Vision, Strategy, Roadmap”). That comma blew the CSV parsing. I replaced the parsing with a Python snippet that uses csv.DictReader
, which respects quoted fields.
Why it actually matters: I save about five clicks and 15 seconds per context switch — but more importantly, it feels like the project lives somewhere. Psychological breadcrumbing. No context tabs. Just type and snap into it.
4. Automating late invoice nudges through Airtable and Gmail
Freelancers live in the no-man’s land between “I’m sure the invoice went out” and “why hasn’t this been paid yet.” I hooked Airtable to Gmail with a scenario that flags overdue invoices and fires polite nudge emails at day 7, 15, and 30. It works — mostly.
Edge case I hit: if the invoice is manually marked as paid between Airtable sync intervals (I ran every 24hr initially), the wrong email can still go out.
So I added a check: right before sending the email, the Zap reruns a filter on the current Airtable ‘Status’ field. If it’s “Paid,” the message doesn’t send. If it’s in “Pending,” it goes. This avoids the classic “Sorry—your invoice was paid last week” reply. Which always comes with more awkwardness than any bounce.
Small UI thing: Gmail’s built-in “Do not repeat” logic actually resets if you edit the message after the first send. Learned this when I updated the email copy slightly for another client and the entire nudge series reset. From then on, I used version-labeled templates saved in Airtable — that way, once sent, they’re immutable even if I update the overall structure.
5. Catching double webhook triggers from Google Forms
This is what made me scrap a Google Forms-based intake process mid-launch.
Form completed → triggers Make webhook → populates Airtable & Notion → sends email. Easy. Except sometimes the webhook would trigger twice for a single submission. I thought it was user error — maybe double-clicking the Submit button? Nope. It was Google re-firing the webhook after Form Responses updated in Sheets, especially when conditional logic changed visibility of fields dynamically.
Confirmed by watching HTTP logs:
POST /hook_id HTTP/1.1
User-Agent: AppsScript
Payload-ID: 64afa822-bee8-499b
Payload: same every time
To fix it, I included a UUID token in each submission, built from timestamp + user email (if supplied). Then Make only processes the first one and discards any duplicate UUID within a moving 60-minute window.
Incidentally, Make doesn’t persist run-specific identifiers unless you store them manually — so I had to connect it to a dedupe table in Airtable acting basically like a cache.
6. Syncing ChatGPT outputs into Notion with frontmatter parsing
I wanted a lightweight brainstorm logger. I’d type something in ChatGPT, ask it to expand or rewrite, then save the output into Notion so I could pull it later. The glue was Make again.
What broke first: Make’s OpenAI module doesn’t include metadata like which prompt triggered it or who typed it unless you manually carry context forward. So unless I wanted orphaned text blobs, I had to structure the interaction beforehand.
Solution: I prompt ChatGPT with a hardcoded frontmatter skeleton, like this:
---
prompt: summarize topic
client: HARLOW
style: dry
---
[actual text here]
The Make scenario then parses the blob using a regex node and slices out the metadata fields. These become database rows in Notion, with a nice little tag system based on ‘style’.
The ‘aha’ moment was realizing ChatGPT won’t break the structure if the frontmatter stays minimal and consistent. Tried YAML, JSON, even TOML — YAML worked best. Be warned: indenting YAML bodies can trigger Markdown rendering issues on longer responses.
7. Triggering context-aware messages in Slack from Notion changes
This one’s hacky but it works. I wanted Slack pings when a specific Notion checkbox flipped (e.g., “Ready for Review”). But Notion ≠ real-time, especially on Zapier, where polling delays matter.
So I used Make, which has tighter control over polling intervals. Still had to deal with chatter: checking and unchecking would create duplicate Slack messages. No built-in debouncing. What saved me was using a separate field called “Last Slack Send” and a filter to compare its timestamp to the update timestamp of the original checkbox.
Only if the update’s time is newer does the system fire. Here’s the relevant formula:
if(updated > last_slack_send, send, halt)
It’s dumb but practical. I also inject the Notion page title, assigned member, and a dynamic Slack message based on category (“Reminder for {name} about {task}”).
I once left a test checkbox on a shared board. Client thought we’d launched the feature. Embarrassing. Added an additional safety toggle: “Live Automation” — boolean gate that has to be on before any message runs.