Four weeks of individual actions. Gmail, Calendar, Tasks, Sheets, Docs, LinkedIn. Today you chain them all into run_workflow. What does the end-of-month client digest need to produce?
Read this month's hours from the Sheet, format a summary email per client and send it to myself, schedule next-month kickoffs from Calendar events into Tasks, draft a LinkedIn post for the projects I wrapped.
Exactly the capstone from the project scaffold. Each step calls a function you built this track. The workflow returns a summary dict of what was sent, created, and posted — your audit trail for the month:
def run_workflow(sheet_id: str, cal_id: str, task_list_id: str) -> dict:
email_result = email_sheet_summary(sheet_id, 'me@myself.com')
tasks = upcoming_to_tasks(cal_id, task_list_id)
post_result = toolset.execute_action(Action.LINKEDIN_CREATE_LINKED_IN_POST,
{'text': 'Month wrapped. Projects delivered. Invoices sent.'})
return {
'emails_sent': 1,
'tasks_created': len(tasks),
'linkedin_posted': bool(post_result.get('id'))
}Why does emails_sent hardcode to 1? If there are multiple clients, shouldn't it count each one?
In the full implementation you loop over clients and call safe_send for each, then count the successes. The hardcoded 1 here is a starting scaffold — replace it with len(successful_sends) once you have the loop working. Here is that extension:
emails_sent = 0
for row in read_range(sheet_id, 'A:D')[1:]:
result = safe_send('me@myself.com', f'Hours: {row[1]}', row[2])
if result.get('status') != 'skipped':
emails_sent += 1So the return dict is a live count of what actually ran — not a guess. If safe_send skipped three clients due to auth errors, the dict shows that.
That is what automation feels like when the plumbing disappears. You defined the workflow once; it runs whenever you call it. The judgment you sold this month is still yours — the boilerplate is not.
I could wire my whole Monday morning in this. Not just the invoices.
What would you automate first — the kickoff prep, the status update emails, or the end-of-project LinkedIn post?
run_workflow chains all four weeks of automation:
rows = read_range(sheet_id, 'A:D') # Sheets: load hours
safe_send(recipient, 'Summary', format_rows(rows)) # Gmail: email per client
events = find_events(cal_id, next_month) # Calendar: upcoming kickoffs
for e in events: add_task(task_list_id, e['summary'], '') # Tasks: create tasksemails_sent — int: how many client emails were deliveredtasks_created — int: how many Tasks entries were createdlinkedin_posted — bool: whether the LinkedIn post succeededIdempotency: the workflow is designed to be run once per month. Running it twice may create duplicate tasks or posts — add a check or a run log if you automate the scheduling.
Four weeks of individual actions. Gmail, Calendar, Tasks, Sheets, Docs, LinkedIn. Today you chain them all into run_workflow. What does the end-of-month client digest need to produce?
Read this month's hours from the Sheet, format a summary email per client and send it to myself, schedule next-month kickoffs from Calendar events into Tasks, draft a LinkedIn post for the projects I wrapped.
Exactly the capstone from the project scaffold. Each step calls a function you built this track. The workflow returns a summary dict of what was sent, created, and posted — your audit trail for the month:
def run_workflow(sheet_id: str, cal_id: str, task_list_id: str) -> dict:
email_result = email_sheet_summary(sheet_id, 'me@myself.com')
tasks = upcoming_to_tasks(cal_id, task_list_id)
post_result = toolset.execute_action(Action.LINKEDIN_CREATE_LINKED_IN_POST,
{'text': 'Month wrapped. Projects delivered. Invoices sent.'})
return {
'emails_sent': 1,
'tasks_created': len(tasks),
'linkedin_posted': bool(post_result.get('id'))
}Why does emails_sent hardcode to 1? If there are multiple clients, shouldn't it count each one?
In the full implementation you loop over clients and call safe_send for each, then count the successes. The hardcoded 1 here is a starting scaffold — replace it with len(successful_sends) once you have the loop working. Here is that extension:
emails_sent = 0
for row in read_range(sheet_id, 'A:D')[1:]:
result = safe_send('me@myself.com', f'Hours: {row[1]}', row[2])
if result.get('status') != 'skipped':
emails_sent += 1So the return dict is a live count of what actually ran — not a guess. If safe_send skipped three clients due to auth errors, the dict shows that.
That is what automation feels like when the plumbing disappears. You defined the workflow once; it runs whenever you call it. The judgment you sold this month is still yours — the boilerplate is not.
I could wire my whole Monday morning in this. Not just the invoices.
What would you automate first — the kickoff prep, the status update emails, or the end-of-project LinkedIn post?
run_workflow chains all four weeks of automation:
rows = read_range(sheet_id, 'A:D') # Sheets: load hours
safe_send(recipient, 'Summary', format_rows(rows)) # Gmail: email per client
events = find_events(cal_id, next_month) # Calendar: upcoming kickoffs
for e in events: add_task(task_list_id, e['summary'], '') # Tasks: create tasksemails_sent — int: how many client emails were deliveredtasks_created — int: how many Tasks entries were createdlinkedin_posted — bool: whether the LinkedIn post succeededIdempotency: the workflow is designed to be run once per month. Running it twice may create duplicate tasks or posts — add a check or a run log if you automate the scheduling.
Create a free account to get started. Paid plans unlock all tracks.