You have read_range from Day 17 and send_email from Day 7. Your advisor wants a weekly progress summary. How do you connect them?
Call read_range first to get the rows, format them into a message body, then call send_email with that body. The two functions are already written — I just sequence them inside a new wrapper.
Exactly. The workflow function calls your existing helpers in order. read_range returns a list of rows — each row is a list of strings. Format them into a readable body, then pass to send_email:
def email_sheet_summary(sheet_id: str, recipient: str) -> dict:
rows = read_range(sheet_id, "Sheet1!A:C")
lines = [" | ".join(row) for row in rows if row]
body = "Progress update:\n\n" + "\n".join(lines)
result = send_email(recipient, "Weekly Research Progress", body)
print(f"Sent summary of {len(rows)} rows to {recipient}")
return resultWhat if read_range returns an empty list — maybe the sheet is blank?
Guard against it. Check if not rows before building the body and return early or send a 'no data' message. Defensive coding applies to cross-app workflows too — each step can independently return empty data:
def email_sheet_summary(sheet_id: str, recipient: str) -> dict:
rows = read_range(sheet_id, "Sheet1!A:C")
if not rows:
return send_email(recipient, "Weekly Research Progress", "No rows found in sheet.")
lines = [" | ".join(row) for row in rows if row]
body = "Progress update:\n\n" + "\n".join(lines)
result = send_email(recipient, "Weekly Research Progress", body)
print(f"Sent summary of {len(rows)} rows to {recipient}")
return resultTwo apps, one function, a dozen lines. The sub-calls are already tested — I'm just composing them.
That's the workflow mindset. Each sub-function is already correct — your job is sequencing and key extraction. The only new complexity is understanding what each step returns and what the next step needs.
I can send this every Friday without opening a browser. The sheet stays in the cloud, the email goes to my advisor, and I don't have to format anything manually.
Correct. The formatting is one-time work embedded in the function. Once written, the workflow runs on any sheet with the same column structure — same function, different data.
read_range(sheet_id, range) → format rows → send_email(to, subject, body)
read_range returns a list[list[str]] — each inner list is one row. Use a list comprehension to join each row with " | " for a readable table-style body.
Always check if not rows before formatting. Empty sheets return []. Return a short "No data" email instead of crashing.
send_email returns a result dict from the Composio action. Return it directly so the caller can inspect the response or message ID.
You have read_range from Day 17 and send_email from Day 7. Your advisor wants a weekly progress summary. How do you connect them?
Call read_range first to get the rows, format them into a message body, then call send_email with that body. The two functions are already written — I just sequence them inside a new wrapper.
Exactly. The workflow function calls your existing helpers in order. read_range returns a list of rows — each row is a list of strings. Format them into a readable body, then pass to send_email:
def email_sheet_summary(sheet_id: str, recipient: str) -> dict:
rows = read_range(sheet_id, "Sheet1!A:C")
lines = [" | ".join(row) for row in rows if row]
body = "Progress update:\n\n" + "\n".join(lines)
result = send_email(recipient, "Weekly Research Progress", body)
print(f"Sent summary of {len(rows)} rows to {recipient}")
return resultWhat if read_range returns an empty list — maybe the sheet is blank?
Guard against it. Check if not rows before building the body and return early or send a 'no data' message. Defensive coding applies to cross-app workflows too — each step can independently return empty data:
def email_sheet_summary(sheet_id: str, recipient: str) -> dict:
rows = read_range(sheet_id, "Sheet1!A:C")
if not rows:
return send_email(recipient, "Weekly Research Progress", "No rows found in sheet.")
lines = [" | ".join(row) for row in rows if row]
body = "Progress update:\n\n" + "\n".join(lines)
result = send_email(recipient, "Weekly Research Progress", body)
print(f"Sent summary of {len(rows)} rows to {recipient}")
return resultTwo apps, one function, a dozen lines. The sub-calls are already tested — I'm just composing them.
That's the workflow mindset. Each sub-function is already correct — your job is sequencing and key extraction. The only new complexity is understanding what each step returns and what the next step needs.
I can send this every Friday without opening a browser. The sheet stays in the cloud, the email goes to my advisor, and I don't have to format anything manually.
Correct. The formatting is one-time work embedded in the function. Once written, the workflow runs on any sheet with the same column structure — same function, different data.
read_range(sheet_id, range) → format rows → send_email(to, subject, body)
read_range returns a list[list[str]] — each inner list is one row. Use a list comprehension to join each row with " | " for a readable table-style body.
Always check if not rows before formatting. Empty sheets return []. Return a short "No data" email instead of crashing.
send_email returns a result dict from the Composio action. Return it directly so the caller can inspect the response or message ID.
Create a free account to get started. Paid plans unlock all tracks.