Two APIs in one function — but this time the second call depends on the first. You read the first email snippet, and the task title comes from what you read. What goes wrong if the inbox is empty?
I'd try to grab messages[0] and crash with an IndexError? So I need an early return before the second call ever fires?
Exactly. Guard the read, bail early with a safe value, then fire the write. The write call only runs when there's something real to send:
emails = toolset.execute_action(Action.GMAIL_FETCH_EMAILS, {"max_results": 1})
messages = emails.get("messages", [])
if not messages:
return ""And the write call passes the snippet as the task title? That's one more execute_action, this time on Google Tasks?
Every Composio write is the same shape as every read — action enum, params dict, response dict. The params just change from read filters to create fields:
def create_task_from_first_email() -> str:
emails = toolset.execute_action(Action.GMAIL_FETCH_EMAILS, {"max_results": 1})
messages = emails.get("messages", [])
if not messages:
return ""
title = messages[0].get("snippet", "")
toolset.execute_action(Action.GOOGLETASKS_CREATE_TASK, {"title": title})
return titleThe function returns the title instead of the task id — why return the input, not the output of the write?
Returning the title makes the function easy to log and easy to test: the caller sees exactly what was added. The task id is a server-assigned detail — useful when you need to delete or update the task later, not on the first write. Pick the return that matches what the caller actually does with it.
So the shape is read, guard, write, return — the first time my Python code changes something real in my account?
Live task, your account, every run. The guarded-chain pattern is the skeleton for every read-then-write in the next two weeks. Swap the read API, swap the write API, the shape stays.
TL;DR: Guard the read, early-return if empty, write only with real data.
max_results: 1if not messages: return "" kills the empty-list caseexecute_action with a create action| Return | Use case |
|---|---|
| Input title | Logging, testing — caller sees what was added |
| Server task id | Chaining updates — needed to delete or modify later |
| Boolean | Minimal — "did anything happen?" |
Match the return to what your caller does next. For this lesson, the title is the most useful handle.
Two APIs in one function — but this time the second call depends on the first. You read the first email snippet, and the task title comes from what you read. What goes wrong if the inbox is empty?
I'd try to grab messages[0] and crash with an IndexError? So I need an early return before the second call ever fires?
Exactly. Guard the read, bail early with a safe value, then fire the write. The write call only runs when there's something real to send:
emails = toolset.execute_action(Action.GMAIL_FETCH_EMAILS, {"max_results": 1})
messages = emails.get("messages", [])
if not messages:
return ""And the write call passes the snippet as the task title? That's one more execute_action, this time on Google Tasks?
Every Composio write is the same shape as every read — action enum, params dict, response dict. The params just change from read filters to create fields:
def create_task_from_first_email() -> str:
emails = toolset.execute_action(Action.GMAIL_FETCH_EMAILS, {"max_results": 1})
messages = emails.get("messages", [])
if not messages:
return ""
title = messages[0].get("snippet", "")
toolset.execute_action(Action.GOOGLETASKS_CREATE_TASK, {"title": title})
return titleThe function returns the title instead of the task id — why return the input, not the output of the write?
Returning the title makes the function easy to log and easy to test: the caller sees exactly what was added. The task id is a server-assigned detail — useful when you need to delete or update the task later, not on the first write. Pick the return that matches what the caller actually does with it.
So the shape is read, guard, write, return — the first time my Python code changes something real in my account?
Live task, your account, every run. The guarded-chain pattern is the skeleton for every read-then-write in the next two weeks. Swap the read API, swap the write API, the shape stays.
TL;DR: Guard the read, early-return if empty, write only with real data.
max_results: 1if not messages: return "" kills the empty-list caseexecute_action with a create action| Return | Use case |
|---|---|
| Input title | Logging, testing — caller sees what was added |
| Server task id | Chaining updates — needed to delete or modify later |
| Boolean | Minimal — "did anything happen?" |
Match the return to what your caller does next. For this lesson, the title is the most useful handle.
Create a free account to get started. Paid plans unlock all tracks.