Week 1 was reads — calls that ask the API for information. Week 2 is writes — calls that change something on the other side. Send an email, append a row, create an event. The first one: send.
toolset.execute_action(Action.GMAIL_SEND_EMAIL, {
"recipient_email": "yourself@example.com",
"subject": "zuzu-day-8",
"body": "hello from python",
})A real email lands in a real inbox. Side effects are committed — there's no "undo".
So before week 2, my code couldn't really break anything?
Reads have minor effects (rate limit, quota slot) but no permanent change. Writes do. Pause-and-check before running any new send. Especially with loops.
And how do we verify the send happened?
Round trip. Send the message, then search the inbox for the subject we used. If the API call worked AND Gmail indexed the message, the search finds it. The grading post-script does this — a small retry loop in case Gmail's index hasn't caught up yet.
Reads return information. Writes do something — send a message, create a row, modify an event. Once the call returns successfully, the change is committed. No rollback.
Same execute_action shape as week 1, just a different action slug:
toolset.execute_action(Action.GMAIL_SEND_EMAIL, {
"recipient_email": "someone@example.com",
"subject": "My subject",
"body": "plain text body",
})recipient_email, subject, body are required. (Optional: cc, bcc, attachments.)
A loop that calls a write API is a loop that creates real-world artifacts. Consider:
for i in range(100):
toolset.execute_action(Action.GMAIL_SEND_EMAIL, {...}) # 100 emailsA classic week-2 panic is a typo'd recipient and 100 emails to your boss. As a rule for early lessons:
range(2), not range(100)print(f"about to send to {recipient}") and visually confirm before unleashingGmail's send is fast. The corresponding messages.list index update is eventually consistent — your sent email may take 5-30 seconds to be searchable by subject. The grading post-script accounts for this with a retry loop:
import time
for _ in range(5):
found = toolset.execute_action(Action.GMAIL_FETCH_EMAILS,
{"query": "subject:zuzu-day-8"})
if found.get("messages"):
break
time.sleep(2)
assert found.get("messages"), "sent email not found in inbox"When authoring side_effect verifications, retry-with-sleep is the standard pattern. (The lesson-authoring-rules.md document captures this for future authors.)
Week 1 was reads — calls that ask the API for information. Week 2 is writes — calls that change something on the other side. Send an email, append a row, create an event. The first one: send.
toolset.execute_action(Action.GMAIL_SEND_EMAIL, {
"recipient_email": "yourself@example.com",
"subject": "zuzu-day-8",
"body": "hello from python",
})A real email lands in a real inbox. Side effects are committed — there's no "undo".
So before week 2, my code couldn't really break anything?
Reads have minor effects (rate limit, quota slot) but no permanent change. Writes do. Pause-and-check before running any new send. Especially with loops.
And how do we verify the send happened?
Round trip. Send the message, then search the inbox for the subject we used. If the API call worked AND Gmail indexed the message, the search finds it. The grading post-script does this — a small retry loop in case Gmail's index hasn't caught up yet.
Reads return information. Writes do something — send a message, create a row, modify an event. Once the call returns successfully, the change is committed. No rollback.
Same execute_action shape as week 1, just a different action slug:
toolset.execute_action(Action.GMAIL_SEND_EMAIL, {
"recipient_email": "someone@example.com",
"subject": "My subject",
"body": "plain text body",
})recipient_email, subject, body are required. (Optional: cc, bcc, attachments.)
A loop that calls a write API is a loop that creates real-world artifacts. Consider:
for i in range(100):
toolset.execute_action(Action.GMAIL_SEND_EMAIL, {...}) # 100 emailsA classic week-2 panic is a typo'd recipient and 100 emails to your boss. As a rule for early lessons:
range(2), not range(100)print(f"about to send to {recipient}") and visually confirm before unleashingGmail's send is fast. The corresponding messages.list index update is eventually consistent — your sent email may take 5-30 seconds to be searchable by subject. The grading post-script accounts for this with a retry loop:
import time
for _ in range(5):
found = toolset.execute_action(Action.GMAIL_FETCH_EMAILS,
{"query": "subject:zuzu-day-8"})
if found.get("messages"):
break
time.sleep(2)
assert found.get("messages"), "sent email not found in inbox"When authoring side_effect verifications, retry-with-sleep is the standard pattern. (The lesson-authoring-rules.md document captures this for future authors.)
Create a free account to get started. Paid plans unlock all tracks.