Campaign
Guavadialer is the SDK for creating and running high-volume outbound calling campaigns programmatically. Define campaigns, upload contacts, write per-call logic via Agent callbacks, and serve campaigns — all from Python.
Note that you need outbound permission approval to use outbound dialing; please contact support@goguava.ai.
Campaign lifecycle
A campaign is created with create_or_update_campaign(), which upserts by name and returns an Campaign object. From there you upload contacts, serve the campaign, and monitor status.
Campaign configuration
| Parameter | Type | Default | Description |
|---|---|---|---|
| campaign_name | str | — | Unique campaign name per organization. |
| origin_phone_numbers | list[str] | — | E.164 numbers owned by you. Calls are round-robin distributed across them. |
| calling_windows | list[dict] | — | Each dict: day (0–6 or name like "monday"), start_time ("HH:MM"), end_time ("HH:MM"). Times are in campaign timezone. |
| start_date | str | — | "YYYY-MM-DD" — when the campaign begins. |
| end_date | str | None | None | "YYYY-MM-DD" — optional end date. |
| max_concurrency | int | 1 | Maximum simultaneous active calls. |
| max_attempts | int | 1 | Maximum call attempts per contact before marking failed. |
| timezone | str | "America/Los_Angeles" | IANA timezone for calling windows. |
| description | str | "" | Campaign description. Required when using Agentic Tenacity. |
SDK methods
| Parameter | Type | Default | Description |
|---|---|---|---|
| create_or_update_campaign() | function | — | Upserts a campaign by name. Returns an Campaign object. If the campaign already existed, updates with any provided fields |
| list_campaigns() | function | — | Returns all campaigns for the authenticated user. |
| campaign.upload_contacts() | method | — | Uploads contacts. Each contact is a Contact(phone_number="+1...", data={...}) object. accepted_terms_of_service must be True. Pass outreach_modalities to enable multi-channel outreach (see Agentic Tenacity). allow_duplicates is also available. |
| campaign.get_status() | method | — | Returns contact status histogram (trying, completed, partially_completed, failed). |
| campaign.update() | method | — | Updates mutable campaign fields (concurrency, calling windows, etc.). |
| campaign.delete() | method | — | Soft-deletes the campaign. |
Contact statuses
| Status | Meaning |
|---|---|
| trying | Eligible for dispatch |
| completed | Call finished successfully |
| partially_completed | Call connected but not all tasks done |
| failed | Permanent error or max attempts exhausted |
| do_not_call | Contact has asked not to be called |
Agent callbacks
Define an Agent and attach callbacks using decorators. Per-call data from the contact's data dict is available via call.get_variable(). Use on_call_start to initiate contact, on_reach_person to branch on availability, and on_task_complete to wrap up.
Serving a campaign
Use agent.attach_campaign(campaign=campaign) to start processing calls. This blocks and handles calls as they are dispatched by the autodialer. Multiple processes can run in parallel for horizontal scaling.
The autodialer dispatches calls every 2 minutes within configured calling windows. Calls that fail with retryable SIP errors are automatically rescheduled up to max_attempts.
attach_campaign polls v1/campaigns/{id}/has-callable-contacts every 5 seconds. When no callable contacts remain and no active calls exist, the socket closes cleanly and attach_campaign returns with a log: INFO: Campaign '<name>' has no more callable contacts and no active calls. Closing. (Added in v0.21; previously attach_campaign blocked indefinitely until the process was killed.)
Complete example
import os
import guava
from guava import Agent, Field
from guava.campaigns import get_or_create_campaign, Contact
# 1. Create or retrieve a campaign
campaign = get_or_create_campaign(
"political-poll-q2-2026",
origin_phone_numbers=[os.environ["GUAVA_AGENT_NUMBER"]],
calling_windows=[
{"day": day, "start_time": "09:00", "end_time": "17:00"}
for day in ["monday", "tuesday", "wednesday", "thursday", "friday"]
],
start_date="2026-04-01",
max_concurrency=3,
max_attempts=2,
)
# 2. Upload contacts
campaign.upload_contacts(
[
Contact(phone_number="+15551234567", data={"first_name": "Alice", "district": "District 5"}),
Contact(phone_number="+15559876543", data={"first_name": "Bob", "district": "District 12"}),
],
accepted_terms_of_service=True,
)
# 3. Define call logic
agent = Agent(
name="Jordan",
organization="National Opinion Research Center",
purpose="Conduct a non-partisan political opinion poll.",
)
@agent.on_call_start
def on_call_start(call: guava.Call):
first_name = call.get_variable("first_name")
call.reach_person(
contact_full_name=first_name,
greeting=(
f"Hi, is this {first_name}? I'm calling from the National Opinion Research Center "
"about issues affecting your district. Would you have two minutes to participate?"
),
)
@agent.on_reach_person
def on_reach_person(call: guava.Call, outcome: str):
if outcome == "unavailable":
call.hangup()
elif outcome == "available":
first_name = call.get_variable("first_name")
district = call.get_variable("district")
call.set_task(
"political_poll",
objective=(
f"Conduct a brief political opinion poll with {first_name} "
f"in {district}. Be polite and non-partisan."
),
checklist=[
Field(
key="willing_to_participate",
description="Whether the respondent agrees to take the poll",
field_type="multiple_choice",
choices=["yes", "no"],
),
"If they said no, thank them and end the call.",
Field(
key="top_issue",
description="Most important issue to the respondent",
question=f"What is the most important issue facing {district}?",
field_type="multiple_choice",
choices=["economy", "healthcare", "education", "housing",
"public_safety", "environment", "other"],
),
Field(
key="likely_to_vote",
description="How likely they are to vote",
question="How likely are you to vote in the upcoming election?",
field_type="multiple_choice",
choices=["very_likely", "likely", "unlikely", "very_unlikely"],
),
],
)
@agent.on_task_complete("political_poll")
def on_poll_complete(call: guava.Call):
call.hangup("Thank them for participating.")
# 4. Serve the campaign (blocks until campaign completes or process is stopped)
agent.attach_campaign(campaign=campaign)GUAVA_API_KEY env var set to a valid API key. The base URL defaults to production; override with GUAVA_BASE_URL.Voicemail detection and handling
Not every outbound call reaches a live person, and that's fine. Rather than using reach_person() (which simply succeeds or fails), you can build custom routing logic to handle voicemail, wrong numbers, and do-not-contact requests, turning every call outcome into a useful action.
The pattern: use a Field with field_type="multiple_choice" to classify who answered — the target contact, someone else, voicemail, or a wrong number. Then route to the appropriate handler based on the result.
read_script() — delivers a scripted message verbatim (no LLM improvisation). Ideal for voicemail messages where you need precise, compliant wording.
Multi-phase tasks. Call set_task() again from on_complete to chain tasks. The first task identifies who answered; the second task handles the actual conversation or voicemail.
Graceful exits. Handle every outcome — confirmed identity, unavailable, wrong number, do-not-contact — so no call ends awkwardly or without logging.
import os
import logging
import guava
from guava import Agent, Field, Say
from guava.campaigns import create_or_update_campaign, Contact
logging.basicConfig(level=logging.INFO)
agent = Agent(
name="Sarah",
organization="Valley Health",
purpose="Remind patients about upcoming appointments",
)
@agent.on_call_start
def on_call_start(call: guava.Call):
patient_name = call.get_variable("patient_name")
call.reach_person(
contact_full_name=patient_name,
greeting=(
f"Hello, this is Sarah calling from Valley Health. "
f"May I please speak with {patient_name}?"
),
)
@agent.on_reach_person
def on_reach_person(call: guava.Call, outcome: str):
patient_name = call.get_variable("patient_name")
appointment_date = call.get_variable("appointment_date")
appointment_time = call.get_variable("appointment_time")
office_number = call.get_variable("office_number")
if outcome == "unavailable":
call.read_script(
f"Hello, this is a message for {patient_name}. "
f"This is Sarah calling from Valley Health. "
f"This is a friendly reminder about your appointment on "
f"{appointment_date} at {appointment_time}. "
f"Please call us at {office_number} if you need to make any changes. "
f"Thank you!"
)
call.hangup()
elif outcome == "available":
call.set_task(
"confirm_appointment",
objective=f"Confirm the appointment with {patient_name}.",
checklist=[
Say(
f"Great! I'm calling to remind you about your appointment on "
f"{appointment_date} at {appointment_time}."
),
Field(
key="appointment_response",
description="Whether the patient confirms, reschedules, or cancels",
field_type="multiple_choice",
choices=["confirmed", "reschedule", "cancel"],
),
],
)
@agent.on_task_complete("confirm_appointment")
def on_appointment_confirmed(call: guava.Call):
response = call.get_field("appointment_response")
logging.info("Appointment response: %s", response)
call.hangup("Thank them and end the call.")
if __name__ == "__main__":
campaign = create_or_update_campaign(
"appointment-reminders",
origin_phone_numbers=[os.environ["GUAVA_AGENT_NUMBER"]],
calling_windows=[
{"day": day, "start_time": "09:00", "end_time": "17:00"}
for day in ["monday", "tuesday", "wednesday", "thursday", "friday"]
],
start_date="2026-04-01",
max_concurrency=3,
max_attempts=2,
)
campaign.upload_contacts(
[
Contact(
phone_number="+15551234567",
data={
"patient_name": "Alice Johnson",
"appointment_date": "April 22nd",
"appointment_time": "2:30 PM",
"office_number": "(555) 123-4567",
},
),
],
accepted_terms_of_service=True,
)
agent.attach_campaign(campaign=campaign)Questions? hi@goguava.ai
import os
import guava
from guava import Agent, Field
from guava.campaigns import get_or_create_campaign, Contact
# 1. Create or retrieve a campaign
campaign = get_or_create_campaign(
"political-poll-q2-2026",
origin_phone_numbers=[os.environ["GUAVA_AGENT_NUMBER"]],
calling_windows=[
{"day": day, "start_time": "09:00", "end_time": "17:00"}
for day in ["monday", "tuesday", "wednesday", "thursday", "friday"]
],
start_date="2026-04-01",
max_concurrency=3,
max_attempts=2,
)
# 2. Upload contacts
campaign.upload_contacts(
[
Contact(phone_number="+15551234567", data={"first_name": "Alice", "district": "District 5"}),
Contact(phone_number="+15559876543", data={"first_name": "Bob", "district": "District 12"}),
],
accepted_terms_of_service=True,
)
# 3. Define call logic
agent = Agent(
name="Jordan",
organization="National Opinion Research Center",
purpose="Conduct a non-partisan political opinion poll.",
)
@agent.on_call_start
def on_call_start(call: guava.Call):
first_name = call.get_variable("first_name")
call.reach_person(
contact_full_name=first_name,
greeting=(
f"Hi, is this {first_name}? I'm calling from the National Opinion Research Center "
"about issues affecting your district. Would you have two minutes to participate?"
),
)
@agent.on_reach_person
def on_reach_person(call: guava.Call, outcome: str):
if outcome == "unavailable":
call.hangup()
elif outcome == "available":
first_name = call.get_variable("first_name")
district = call.get_variable("district")
call.set_task(
"political_poll",
objective=(
f"Conduct a brief political opinion poll with {first_name} "
f"in {district}. Be polite and non-partisan."
),
checklist=[
Field(
key="willing_to_participate",
description="Whether the respondent agrees to take the poll",
field_type="multiple_choice",
choices=["yes", "no"],
),
"If they said no, thank them and end the call.",
Field(
key="top_issue",
description="Most important issue to the respondent",
question=f"What is the most important issue facing {district}?",
field_type="multiple_choice",
choices=["economy", "healthcare", "education", "housing",
"public_safety", "environment", "other"],
),
Field(
key="likely_to_vote",
description="How likely they are to vote",
question="How likely are you to vote in the upcoming election?",
field_type="multiple_choice",
choices=["very_likely", "likely", "unlikely", "very_unlikely"],
),
],
)
@agent.on_task_complete("political_poll")
def on_poll_complete(call: guava.Call):
call.hangup("Thank them for participating.")
# 4. Serve the campaign (blocks until campaign completes or process is stopped)
agent.attach_campaign(campaign=campaign)import os
import logging
import guava
from guava import Agent, Field, Say
from guava.campaigns import create_or_update_campaign, Contact
logging.basicConfig(level=logging.INFO)
agent = Agent(
name="Sarah",
organization="Valley Health",
purpose="Remind patients about upcoming appointments",
)
@agent.on_call_start
def on_call_start(call: guava.Call):
patient_name = call.get_variable("patient_name")
call.reach_person(
contact_full_name=patient_name,
greeting=(
f"Hello, this is Sarah calling from Valley Health. "
f"May I please speak with {patient_name}?"
),
)
@agent.on_reach_person
def on_reach_person(call: guava.Call, outcome: str):
patient_name = call.get_variable("patient_name")
appointment_date = call.get_variable("appointment_date")
appointment_time = call.get_variable("appointment_time")
office_number = call.get_variable("office_number")
if outcome == "unavailable":
call.read_script(
f"Hello, this is a message for {patient_name}. "
f"This is Sarah calling from Valley Health. "
f"This is a friendly reminder about your appointment on "
f"{appointment_date} at {appointment_time}. "
f"Please call us at {office_number} if you need to make any changes. "
f"Thank you!"
)
call.hangup()
elif outcome == "available":
call.set_task(
"confirm_appointment",
objective=f"Confirm the appointment with {patient_name}.",
checklist=[
Say(
f"Great! I'm calling to remind you about your appointment on "
f"{appointment_date} at {appointment_time}."
),
Field(
key="appointment_response",
description="Whether the patient confirms, reschedules, or cancels",
field_type="multiple_choice",
choices=["confirmed", "reschedule", "cancel"],
),
],
)
@agent.on_task_complete("confirm_appointment")
def on_appointment_confirmed(call: guava.Call):
response = call.get_field("appointment_response")
logging.info("Appointment response: %s", response)
call.hangup("Thank them and end the call.")
if __name__ == "__main__":
campaign = create_or_update_campaign(
"appointment-reminders",
origin_phone_numbers=[os.environ["GUAVA_AGENT_NUMBER"]],
calling_windows=[
{"day": day, "start_time": "09:00", "end_time": "17:00"}
for day in ["monday", "tuesday", "wednesday", "thursday", "friday"]
],
start_date="2026-04-01",
max_concurrency=3,
max_attempts=2,
)
campaign.upload_contacts(
[
Contact(
phone_number="+15551234567",
data={
"patient_name": "Alice Johnson",
"appointment_date": "April 22nd",
"appointment_time": "2:30 PM",
"office_number": "(555) 123-4567",
},
),
],
accepted_terms_of_service=True,
)
agent.attach_campaign(campaign=campaign)