Appointment Reminder
This example shows how to use Guava to call patients and remind them of upcoming appointments. If a patient needs to reschedule, the agent checks your Amazon Connect queue in real time — and either transfers them directly to a live scheduling agent (if one is free) or captures their callback preference and creates a Connect task (if the queue is busy).
What Happens on the Call
Prerequisites
- Python 3.10 or later
- A Guava account with an API key and a phone number — sign up at app.goguava.ai
- An AWS account with an Amazon Connect instance
Step 1: Install Guava
Choose whichever package manager you prefer:
pip install guava-sdk # Install using pip
uv add guava-sdk # Install using uv
poetry add guava-sdk # Install using poetryYou also need boto3 for the Amazon Connect API:
pip install boto3Step 2: Set Up Amazon Connect
If you already have a Connect instance configured, skip to the parts you haven't done yet.
2a. Create a Connect Instance
- Open the Amazon Connect console.
- Click Create instance and follow the setup wizard.
- Once the instance is created, open it and copy the Instance ID from the ARN shown in the overview — it's the UUID at the end:
arn:aws:connect:us-east-1:123456789012:instance/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This is your CONNECT_INSTANCE_ID
2b. Create a Contact Flow for Task Routing
Amazon Connect tasks need a dedicated contact flow — the default queue flow does not support tasks.
- In your Connect instance, go to Routing → Contact flows → Create contact flow.
- Name it something like
Guava Task Routing Flow. - Add a Set working queue block and connect it to your scheduling queue.
- Add a Transfer to queue block after that.
- Add a Disconnect / hang up block at the end of the error branch.
- Save and publish the flow.
- Open the flow, click Show additional flow information, and copy the Contact flow ID (the UUID at the end of the ARN).
Tip: For a minimal test flow, you can also use any existing inbound contact flow — tasks will be routed to whatever queue the flow sets.
2c. Set Up a Scheduling Queue
- Go to Routing → Queues → Add new queue.
- Name it (e.g.,
Scheduling) and assign it to the hours of operation and outbound caller ID of your choice. - Copy the Queue ID from the queue's ARN (the UUID at the end).
2d. Get a Transfer Number
This is the phone number Guava will transfer rescheduling patients to — typically your scheduling desk or the Amazon Connect direct dial number for your scheduling queue.
- In Connect, go to Channels → Phone numbers to see claimed numbers.
- Or use any external scheduling desk number in E.164 format (e.g.,
+15551234567).
Step 3: Configure AWS Credentials
The example uses boto3, which reads credentials from the standard AWS credential chain. The easiest way for local development:
export AWS_ACCESS_KEY_ID="your-access-key-id"
export AWS_SECRET_ACCESS_KEY="your-secret-access-key"
export AWS_DEFAULT_REGION="us-east-1" # Match your Connect instance regionThe IAM user or role you use needs the following permissions:
connect:GetCurrentMetricData— to check queue availabilityconnect:StartTaskContact— to create callback tasks
A minimal IAM policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"connect:GetCurrentMetricData",
"connect:StartTaskContact"
],
"Resource": "arn:aws:connect:*:*:instance/YOUR_INSTANCE_ID/*"
}
]
}Step 4: Set Environment Variables
# Guava
export GUAVA_API_KEY="your-guava-api-key"
export GUAVA_AGENT_NUMBER="+15551000000" # Your Guava phone number
# Amazon Connect
export CONNECT_INSTANCE_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export CONNECT_CONTACT_FLOW_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export CONNECT_QUEUE_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export CONNECT_TRANSFER_NUMBER="+15551234567" # Scheduling desk numberStep 5: Run the Example
python -m examples.integrations.ccaas.amazon_connect.appointment_reminder \
+15559876543 \
--name "Jane Doe" \
--appointment "Thursday, April 17 at 10:30 AM"Replace +15559876543 with the phone number to call and adjust --name and --appointment as needed.
What to expect:
- Guava calls the patient and verifies they're the right person
- If they confirm: you'll see a confirmation logged and an SMS is sent
- If they need to reschedule: the example checks your Connect queue in real time
- If agents are free: Guava transfers the patient to
CONNECT_TRANSFER_NUMBER - If the queue is busy: Guava collects a callback preference and creates a task in your Connect instance — visible to agents in the Contact Control Panel (CCP)
- If agents are free: Guava transfers the patient to
- If no one picks up: Guava leaves a voicemail
How It Works
1. Defining the Agent and Kicking Off the Call
agent = Agent(
name="Alex",
organization="Bright Valley Medical Center",
purpose="to remind patients of their upcoming appointments and assist with rescheduling when needed",
)
agent.outbound_phone(
from_number=os.environ["GUAVA_AGENT_NUMBER"],
to_number=args.phone,
variables={"patient_name": args.name, "appointment": args.appointment},
).run()Agent is the top-level handle for the AI persona. outbound_phone places the call and binds the agent to that single conversation. The variables dict is passed into the call and made available throughout — handlers can read each value with call.get_variable(key).
2. Reaching the Right Person (reach_person)
@agent.on_call_start
def on_call_start(call: guava.Call) -> None:
call.reach_person(contact_full_name=call.get_variable("patient_name"))reach_person handles the gatekeeper problem — it has the agent confirm they're speaking with the intended patient before proceeding. If a family member answers, the agent politely asks for the patient by name. The reach result is delivered to the handler registered with @agent.on_reach_person.
@agent.on_reach_person
def on_reach_person(call: guava.Call, outcome: str) -> None:
if outcome == "unavailable":
call.hangup("Leave a brief voicemail ...")
return
# otherwise, set up the reminder task3. Collecting the Appointment Response
call.set_task(
"remind",
objective=...,
checklist=[
guava.Say(f"Hi {patient_name}, this is Alex calling from ..."),
guava.Field(
key="response",
description="Ask if they can make the appointment or need to reschedule.",
field_type="text",
required=True,
),
],
)set_task gives the agent a named objective and a checklist of items to work through. guava.Field tells the agent what information to collect — the patient might say "Yes, I'll be there" or "Actually, I have a conflict that day" — and the agent normalizes that into a structured value you can inspect via call.get_field(...).
4. Classifying the Patient's Response
from guava.helpers.openai import IntentRecognizer
intent_recognizer = IntentRecognizer(["confirmed", "needs to reschedule"])
@agent.on_task_complete("remind")
def handle_response(call: guava.Call) -> None:
response = call.get_field("response", "")
if intent_recognizer.classify(response) == "needs to reschedule":
handle_reschedule(call)
else:
...When the remind task completes, the registered @agent.on_task_complete("remind") handler runs. Rather than matching against a list of keywords, IntentRecognizer uses an LLM to classify what the patient actually meant — natural phrasing like "I actually have a conflict that day" or "Can we move it?" still maps to needs to reschedule without enumerating every variant. The recognizer is instantiated once at module level and reused across calls.
5. Checking the Connect Queue in Real Time
def check_queue_availability() -> dict:
response = connect_client.get_current_metric_data(
InstanceId=os.environ["CONNECT_INSTANCE_ID"],
Filters={"Queues": [os.environ["CONNECT_QUEUE_ID"]], "Channels": ["VOICE"]},
CurrentMetrics=[
{"Name": "AGENTS_AVAILABLE", "Unit": "COUNT"},
{"Name": "CONTACTS_IN_QUEUE", "Unit": "COUNT"},
],
)
...GetCurrentMetricData returns a real-time snapshot of the queue — how many agents are available and how many contacts are already waiting. This snapshot updates every ~15 seconds.
6. Routing Decision: Transfer vs. Task
queue = check_queue_availability()
if queue["agents_available"] > 0:
call.transfer(os.environ["CONNECT_TRANSFER_NUMBER"], "...")
else:
# collect callback preference, then create a task
call.set_task("callback", ...)If a scheduler is free, Guava transfers the call live. If not, the agent collects the patient's preferred callback time in a follow-up callback task; its completion handler creates a Connect task via StartTaskContact — complete with the patient's name, original appointment, and preferred callback window. The task appears in your agents' Contact Control Panel (CCP) just like any other contact.
7. SMS Confirmation
call_state = {"appointment_confirmed": False}
# ... inside the "remind" handler, when the patient confirms:
call_state["appointment_confirmed"] = True
# ... after agent.run() returns:
if call_state["appointment_confirmed"]:
guava.Client().send_sms(...)After agent.run() returns, if the patient confirmed, we send an SMS reminder via the Guava client. This runs after the call ends, so it never blocks the voice interaction.
Customization Ideas
Pull patient data from a database or CRM
Replace the hardcoded --name and --appointment CLI args with a database query or CRM API call, and call patients in a loop:
for patient in get_todays_appointments():
agent.outbound_phone(
from_number=os.environ["GUAVA_AGENT_NUMBER"],
to_number=patient["phone"],
variables={"patient_name": patient["name"], "appointment": patient["appointment"]},
).run()Set a queue depth threshold Instead of transferring whenever any agent is free, add a threshold — e.g., only transfer if fewer than 3 contacts are already waiting:
if queue["agents_available"] > 0 and queue["contacts_in_queue"] < 3:
call.transfer(...)Route by appointment type Different appointment types may need different queues or transfer numbers. Pass the appointment type into the call as a variable and route accordingly:
transfer_number = SPECIALIST_LINE if call.get_variable("type") == "specialist" else GENERAL_LINE
call.transfer(transfer_number, "...")Add multi-language support
Guava supports English, Spanish, French, German, and Italian. The agent's voice can be set per call using the persona configuration on the underlying Call object.
Complete Example
import guava
import os
import logging
import json
import argparse
import boto3
from datetime import datetime
from guava import Agent, logging_utils
from guava.helpers.openai import IntentRecognizer
logger = logging.getLogger("appointment_reminder")
connect_client = boto3.client("connect")
intent_recognizer = IntentRecognizer(["confirmed", "needs to reschedule"])
# State the SMS step needs after the call ends.
call_state: dict = {"appointment_confirmed": False}
agent = Agent(
name="Alex",
organization="Bright Valley Medical Center",
purpose=(
"to remind patients of their upcoming appointments and assist "
"with rescheduling when needed"
),
)
def check_queue_availability() -> dict:
"""Query Amazon Connect for real-time agent availability in the scheduling queue."""
try:
response = connect_client.get_current_metric_data(
InstanceId=os.environ["CONNECT_INSTANCE_ID"],
Filters={
"Queues": [os.environ["CONNECT_QUEUE_ID"]],
"Channels": ["VOICE"],
},
CurrentMetrics=[
{"Name": "AGENTS_AVAILABLE", "Unit": "COUNT"},
{"Name": "CONTACTS_IN_QUEUE", "Unit": "COUNT"},
],
)
agents_available = 0
contacts_in_queue = 0
for result in response.get("MetricResults", []):
for collection in result.get("Collections", []):
name = collection["Metric"]["Name"]
value = collection.get("Value") or 0
if name == "AGENTS_AVAILABLE":
agents_available = int(value)
elif name == "CONTACTS_IN_QUEUE":
contacts_in_queue = int(value)
logger.info(
"Queue check — agents available: %d, contacts in queue: %d",
agents_available,
contacts_in_queue,
)
return {"agents_available": agents_available, "contacts_in_queue": contacts_in_queue}
except Exception as e:
logger.error("Failed to fetch queue metrics: %s", e)
return {"agents_available": 0, "contacts_in_queue": 0}
@agent.on_call_start
def on_call_start(call: guava.Call) -> None:
call.reach_person(contact_full_name=call.get_variable("patient_name"))
@agent.on_reach_person
def on_reach_person(call: guava.Call, outcome: str) -> None:
if outcome == "unavailable":
appointment = call.get_variable("appointment")
call.hangup(
"We were unable to reach the patient. Leave a brief, friendly voicemail from "
"Bright Valley Medical Center reminding them of their appointment on "
f"{appointment} and asking them to call back to confirm or reschedule."
)
return
patient_name = call.get_variable("patient_name")
appointment = call.get_variable("appointment")
call.set_task(
"remind",
objective=(
f"Remind {patient_name} of their appointment at Bright Valley Medical "
f"Center on {appointment}. Ask if they can attend or need to reschedule."
),
checklist=[
guava.Say(
f"Hi {patient_name}, this is Alex calling from Bright Valley "
f"Medical Center with a reminder about your appointment on {appointment}."
),
guava.Field(
key="response",
description=(
"Ask if they can make the appointment or need to reschedule. "
"Capture their answer as 'confirmed' or 'reschedule'."
),
field_type="text",
required=True,
),
],
)
@agent.on_task_complete("remind")
def handle_response(call: guava.Call) -> None:
response = call.get_field("response", "")
if intent_recognizer.classify(response) == "needs to reschedule":
handle_reschedule(call)
else:
call_state["appointment_confirmed"] = True
call.hangup(
"Thank the patient for confirming. Remind them to arrive 15 minutes "
"early and bring their insurance card and a photo ID. Wish them well."
)
def handle_reschedule(call: guava.Call) -> None:
queue = check_queue_availability()
if queue["agents_available"] > 0:
# Scheduling agents are free — transfer directly.
call.transfer(
os.environ["CONNECT_TRANSFER_NUMBER"],
"Let the patient know you're connecting them with a scheduling specialist "
"who will help find a new appointment time. Then transfer the call.",
)
else:
# Queue is busy — collect callback preference and create a task.
call.set_task(
"callback",
objective=(
"Our scheduling team is currently busy. Collect the patient's preferred "
"callback window so a scheduler can call them back."
),
checklist=[
guava.Say(
"Our scheduling team is with other patients right now. "
"I'll arrange for a scheduler to call you back."
),
guava.Field(
key="preferred_callback",
description=(
"Ask when would be a good time for our scheduling team to call "
"them back. Capture their preferred day and time of day."
),
field_type="text",
required=True,
),
],
)
@agent.on_task_complete("callback")
def create_reschedule_task(call: guava.Call) -> None:
patient_name = call.get_variable("patient_name")
appointment = call.get_variable("appointment")
preferred_callback = call.get_field("preferred_callback")
results = {
"timestamp": datetime.utcnow().isoformat() + "Z",
"agent": "Alex",
"organization": "Bright Valley Medical Center",
"use_case": "appointment_reminder_reschedule",
"fields": {
"patient_name": patient_name,
"original_appointment": appointment,
"preferred_callback": preferred_callback,
},
}
print(json.dumps(results, indent=2))
logger.info("Reschedule details captured.")
try:
connect_client.start_task_contact(
InstanceId=os.environ["CONNECT_INSTANCE_ID"],
ContactFlowId=os.environ["CONNECT_CONTACT_FLOW_ID"],
Name=f"Reschedule Needed — {patient_name}"[:512],
Description=(
f"Patient: {patient_name}\n"
f"Original appointment: {appointment}\n"
f"Preferred callback: {preferred_callback}\n"
"Action: Call patient back to reschedule their appointment."
)[:4096],
References={
"source": {"Value": "guava_appointment_reminder", "Type": "STRING"},
},
Attributes={
"patient_name": patient_name,
"original_appointment": appointment,
"preferred_callback": preferred_callback or "",
},
)
logger.info("Amazon Connect reschedule task created successfully.")
except Exception as e:
logger.error("Failed to create Amazon Connect task: %s", e)
call.hangup(
"Let the patient know a scheduling specialist will call them back at their "
"preferred time. Thank them for their patience and wish them a great day."
)
if __name__ == "__main__":
logging_utils.configure_logging()
parser = argparse.ArgumentParser(
description=(
"Outbound appointment reminder with smart escalation for Bright Valley Medical Center. "
"Confirms the appointment, transfers to a live scheduler if one is available, or "
"creates an Amazon Connect callback task if the queue is busy."
)
)
parser.add_argument("phone", help="Patient phone number in E.164 format (e.g. +15551234567)")
parser.add_argument("--name", required=True, help="Full name of the patient")
parser.add_argument(
"--appointment",
default="tomorrow at 9:00 AM",
help="Appointment date and time string (default: 'tomorrow at 9:00 AM')",
)
args = parser.parse_args()
logger.info(
"Calling %s (%s) — appointment: %s",
args.name,
args.phone,
args.appointment,
)
agent.outbound_phone(
from_number=os.environ["GUAVA_AGENT_NUMBER"],
to_number=args.phone,
variables={"patient_name": args.name, "appointment": args.appointment},
).run()
# Send an SMS confirmation if the patient confirmed their appointment.
if call_state["appointment_confirmed"]:
try:
guava.Client().send_sms(
from_number=os.environ["GUAVA_AGENT_NUMBER"],
to_number=args.phone,
message=(
f"Hi {args.name}, this is Bright Valley Medical Center confirming "
f"your appointment on {args.appointment}. Please arrive 15 minutes early "
"and bring your insurance card and photo ID. Reply STOP to opt out."
),
)
logger.info("SMS confirmation sent to %s.", args.phone)
except Exception as e:
logger.error("Failed to send SMS confirmation: %s", e)Questions? hi@goguava.ai
pip install guava-sdk # Install using pip
uv add guava-sdk # Install using uv
poetry add guava-sdk # Install using poetryexport AWS_ACCESS_KEY_ID="your-access-key-id"
export AWS_SECRET_ACCESS_KEY="your-secret-access-key"
export AWS_DEFAULT_REGION="us-east-1" # Match your Connect instance region{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"connect:GetCurrentMetricData",
"connect:StartTaskContact"
],
"Resource": "arn:aws:connect:*:*:instance/YOUR_INSTANCE_ID/*"
}
]
}# Guava
export GUAVA_API_KEY="your-guava-api-key"
export GUAVA_AGENT_NUMBER="+15551000000" # Your Guava phone number
# Amazon Connect
export CONNECT_INSTANCE_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export CONNECT_CONTACT_FLOW_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export CONNECT_QUEUE_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export CONNECT_TRANSFER_NUMBER="+15551234567" # Scheduling desk numberpython -m examples.integrations.ccaas.amazon_connect.appointment_reminder \
+15559876543 \
--name "Jane Doe" \
--appointment "Thursday, April 17 at 10:30 AM"agent = Agent(
name="Alex",
organization="Bright Valley Medical Center",
purpose="to remind patients of their upcoming appointments and assist with rescheduling when needed",
)
agent.outbound_phone(
from_number=os.environ["GUAVA_AGENT_NUMBER"],
to_number=args.phone,
variables={"patient_name": args.name, "appointment": args.appointment},
).run()@agent.on_call_start
def on_call_start(call: guava.Call) -> None:
call.reach_person(contact_full_name=call.get_variable("patient_name"))@agent.on_reach_person
def on_reach_person(call: guava.Call, outcome: str) -> None:
if outcome == "unavailable":
call.hangup("Leave a brief voicemail ...")
return
# otherwise, set up the reminder taskcall.set_task(
"remind",
objective=...,
checklist=[
guava.Say(f"Hi {patient_name}, this is Alex calling from ..."),
guava.Field(
key="response",
description="Ask if they can make the appointment or need to reschedule.",
field_type="text",
required=True,
),
],
)from guava.helpers.openai import IntentRecognizer
intent_recognizer = IntentRecognizer(["confirmed", "needs to reschedule"])
@agent.on_task_complete("remind")
def handle_response(call: guava.Call) -> None:
response = call.get_field("response", "")
if intent_recognizer.classify(response) == "needs to reschedule":
handle_reschedule(call)
else:
...def check_queue_availability() -> dict:
response = connect_client.get_current_metric_data(
InstanceId=os.environ["CONNECT_INSTANCE_ID"],
Filters={"Queues": [os.environ["CONNECT_QUEUE_ID"]], "Channels": ["VOICE"]},
CurrentMetrics=[
{"Name": "AGENTS_AVAILABLE", "Unit": "COUNT"},
{"Name": "CONTACTS_IN_QUEUE", "Unit": "COUNT"},
],
)
...queue = check_queue_availability()
if queue["agents_available"] > 0:
call.transfer(os.environ["CONNECT_TRANSFER_NUMBER"], "...")
else:
# collect callback preference, then create a task
call.set_task("callback", ...)call_state = {"appointment_confirmed": False}
# ... inside the "remind" handler, when the patient confirms:
call_state["appointment_confirmed"] = True
# ... after agent.run() returns:
if call_state["appointment_confirmed"]:
guava.Client().send_sms(...)for patient in get_todays_appointments():
agent.outbound_phone(
from_number=os.environ["GUAVA_AGENT_NUMBER"],
to_number=patient["phone"],
variables={"patient_name": patient["name"], "appointment": patient["appointment"]},
).run()if queue["agents_available"] > 0 and queue["contacts_in_queue"] < 3:
call.transfer(...)transfer_number = SPECIALIST_LINE if call.get_variable("type") == "specialist" else GENERAL_LINE
call.transfer(transfer_number, "...")import guava
import os
import logging
import json
import argparse
import boto3
from datetime import datetime
from guava import Agent, logging_utils
from guava.helpers.openai import IntentRecognizer
logger = logging.getLogger("appointment_reminder")
connect_client = boto3.client("connect")
intent_recognizer = IntentRecognizer(["confirmed", "needs to reschedule"])
# State the SMS step needs after the call ends.
call_state: dict = {"appointment_confirmed": False}
agent = Agent(
name="Alex",
organization="Bright Valley Medical Center",
purpose=(
"to remind patients of their upcoming appointments and assist "
"with rescheduling when needed"
),
)
def check_queue_availability() -> dict:
"""Query Amazon Connect for real-time agent availability in the scheduling queue."""
try:
response = connect_client.get_current_metric_data(
InstanceId=os.environ["CONNECT_INSTANCE_ID"],
Filters={
"Queues": [os.environ["CONNECT_QUEUE_ID"]],
"Channels": ["VOICE"],
},
CurrentMetrics=[
{"Name": "AGENTS_AVAILABLE", "Unit": "COUNT"},
{"Name": "CONTACTS_IN_QUEUE", "Unit": "COUNT"},
],
)
agents_available = 0
contacts_in_queue = 0
for result in response.get("MetricResults", []):
for collection in result.get("Collections", []):
name = collection["Metric"]["Name"]
value = collection.get("Value") or 0
if name == "AGENTS_AVAILABLE":
agents_available = int(value)
elif name == "CONTACTS_IN_QUEUE":
contacts_in_queue = int(value)
logger.info(
"Queue check — agents available: %d, contacts in queue: %d",
agents_available,
contacts_in_queue,
)
return {"agents_available": agents_available, "contacts_in_queue": contacts_in_queue}
except Exception as e:
logger.error("Failed to fetch queue metrics: %s", e)
return {"agents_available": 0, "contacts_in_queue": 0}
@agent.on_call_start
def on_call_start(call: guava.Call) -> None:
call.reach_person(contact_full_name=call.get_variable("patient_name"))
@agent.on_reach_person
def on_reach_person(call: guava.Call, outcome: str) -> None:
if outcome == "unavailable":
appointment = call.get_variable("appointment")
call.hangup(
"We were unable to reach the patient. Leave a brief, friendly voicemail from "
"Bright Valley Medical Center reminding them of their appointment on "
f"{appointment} and asking them to call back to confirm or reschedule."
)
return
patient_name = call.get_variable("patient_name")
appointment = call.get_variable("appointment")
call.set_task(
"remind",
objective=(
f"Remind {patient_name} of their appointment at Bright Valley Medical "
f"Center on {appointment}. Ask if they can attend or need to reschedule."
),
checklist=[
guava.Say(
f"Hi {patient_name}, this is Alex calling from Bright Valley "
f"Medical Center with a reminder about your appointment on {appointment}."
),
guava.Field(
key="response",
description=(
"Ask if they can make the appointment or need to reschedule. "
"Capture their answer as 'confirmed' or 'reschedule'."
),
field_type="text",
required=True,
),
],
)
@agent.on_task_complete("remind")
def handle_response(call: guava.Call) -> None:
response = call.get_field("response", "")
if intent_recognizer.classify(response) == "needs to reschedule":
handle_reschedule(call)
else:
call_state["appointment_confirmed"] = True
call.hangup(
"Thank the patient for confirming. Remind them to arrive 15 minutes "
"early and bring their insurance card and a photo ID. Wish them well."
)
def handle_reschedule(call: guava.Call) -> None:
queue = check_queue_availability()
if queue["agents_available"] > 0:
# Scheduling agents are free — transfer directly.
call.transfer(
os.environ["CONNECT_TRANSFER_NUMBER"],
"Let the patient know you're connecting them with a scheduling specialist "
"who will help find a new appointment time. Then transfer the call.",
)
else:
# Queue is busy — collect callback preference and create a task.
call.set_task(
"callback",
objective=(
"Our scheduling team is currently busy. Collect the patient's preferred "
"callback window so a scheduler can call them back."
),
checklist=[
guava.Say(
"Our scheduling team is with other patients right now. "
"I'll arrange for a scheduler to call you back."
),
guava.Field(
key="preferred_callback",
description=(
"Ask when would be a good time for our scheduling team to call "
"them back. Capture their preferred day and time of day."
),
field_type="text",
required=True,
),
],
)
@agent.on_task_complete("callback")
def create_reschedule_task(call: guava.Call) -> None:
patient_name = call.get_variable("patient_name")
appointment = call.get_variable("appointment")
preferred_callback = call.get_field("preferred_callback")
results = {
"timestamp": datetime.utcnow().isoformat() + "Z",
"agent": "Alex",
"organization": "Bright Valley Medical Center",
"use_case": "appointment_reminder_reschedule",
"fields": {
"patient_name": patient_name,
"original_appointment": appointment,
"preferred_callback": preferred_callback,
},
}
print(json.dumps(results, indent=2))
logger.info("Reschedule details captured.")
try:
connect_client.start_task_contact(
InstanceId=os.environ["CONNECT_INSTANCE_ID"],
ContactFlowId=os.environ["CONNECT_CONTACT_FLOW_ID"],
Name=f"Reschedule Needed — {patient_name}"[:512],
Description=(
f"Patient: {patient_name}\n"
f"Original appointment: {appointment}\n"
f"Preferred callback: {preferred_callback}\n"
"Action: Call patient back to reschedule their appointment."
)[:4096],
References={
"source": {"Value": "guava_appointment_reminder", "Type": "STRING"},
},
Attributes={
"patient_name": patient_name,
"original_appointment": appointment,
"preferred_callback": preferred_callback or "",
},
)
logger.info("Amazon Connect reschedule task created successfully.")
except Exception as e:
logger.error("Failed to create Amazon Connect task: %s", e)
call.hangup(
"Let the patient know a scheduling specialist will call them back at their "
"preferred time. Thank them for their patience and wish them a great day."
)
if __name__ == "__main__":
logging_utils.configure_logging()
parser = argparse.ArgumentParser(
description=(
"Outbound appointment reminder with smart escalation for Bright Valley Medical Center. "
"Confirms the appointment, transfers to a live scheduler if one is available, or "
"creates an Amazon Connect callback task if the queue is busy."
)
)
parser.add_argument("phone", help="Patient phone number in E.164 format (e.g. +15551234567)")
parser.add_argument("--name", required=True, help="Full name of the patient")
parser.add_argument(
"--appointment",
default="tomorrow at 9:00 AM",
help="Appointment date and time string (default: 'tomorrow at 9:00 AM')",
)
args = parser.parse_args()
logger.info(
"Calling %s (%s) — appointment: %s",
args.name,
args.phone,
args.appointment,
)
agent.outbound_phone(
from_number=os.environ["GUAVA_AGENT_NUMBER"],
to_number=args.phone,
variables={"patient_name": args.name, "appointment": args.appointment},
).run()
# Send an SMS confirmation if the patient confirmed their appointment.
if call_state["appointment_confirmed"]:
try:
guava.Client().send_sms(
from_number=os.environ["GUAVA_AGENT_NUMBER"],
to_number=args.phone,
message=(
f"Hi {args.name}, this is Bright Valley Medical Center confirming "
f"your appointment on {args.appointment}. Please arrive 15 minutes early "
"and bring your insurance card and photo ID. Reply STOP to opt out."
),
)
logger.info("SMS confirmation sent to %s.", args.phone)
except Exception as e:
logger.error("Failed to send SMS confirmation: %s", e)