Inspiration

Demo video: https://www.youtube.com/watch?v=-We3QLCGxvM

For 25 years, OpenmindProjects recruited and managed volunteers for our own community projects. Now, we are using that field experience to support independent grassroots hosts who are priced out of software and overwhelmed by constant administrative turnover.

Mindy (AI Agent): Automates volunteer onboarding and scheduling behind a human-approval gate, allowing local hosts to retain total control without the workload.

CALL-E (Voice Layer): Connects Mindy to a phone line. Volunteers apply or book using natural, human-sounding voice conversations that automatically sync with calendars and application logs.

Trust is made in the first impression. From 25 years of field experience: when a volunteer hears someone who sounds native, natural, and professional, they open up and stay. That human-sounding first contact is what a small nonprofit can't staff at every hour. CALL-E bridges that gap.

What it does

"Hello Volunteer" is the voice layer of Mindy, our AI volunteer agent. It's goal-driven and outbound-first: instead of pushing volunteers through a rigid web form or script, we hand CALL-E a real objective (complete an application, book an appointment, confirm a booking) and it handles the call end to end, adapting to the volunteer's answers in real time, behind a human approval gate at every step.

  • Book a callback. A volunteer leaves their number on the "Book a call" form. A host approves with one click ("Approve & call"), then CALL-E calls back with one goal (complete the volunteer's application) and handles the call end to end, adapting to their answers in real time and returning a structured result that becomes their application. A confirmation email goes to the volunteer and the host organization, and the application appears on both dashboards.
  • Apply over the phone. The same callback path guides an applicant through the application and submits it for host review.
  • Review & schedule. The host reviews the application and moves the Pipeline Stage to "Interview Scheduled". Mindy then auto-creates a task for CALL-E, which appears under the CALL-E Agent dashboard, waiting for the host's final approval.
  • Book the appointment. On final approval, CALL-E takes another goal (book the appointment) and books a 30-minute slot into the right Google Calendar (one calendar per type: onsite/internship, online volunteering, partnership), attaches a Google Meet link, and emails a confirmation with a booking number (OMP-000123) for rescheduling.
  • Read your inbox. A verified volunteer can hear their unread messages read back over the phone.
  • Bring your own CALL-E key. Each host can connect their own CALL-E API key (encrypted at rest, masked in the UI, with a "Test key" action), so calls bill to the host's own account and fall back to OpenmindProjects' key when absent.

Every application stays human-in-the-loop: the team reviews each one and signs off before CALL-E takes any action.

We also built the inbound side: a hotline (+1 xxx-xxx-xxx) so volunteers can call us directly. That path is code-complete and ready to ship; it's blocked only by a CALL-E vendor issue (no inbound result webhook, #399) until our toll-free number is bound.

Flow diagrams

Every path runs one core loop (start_call (POST) → poll to a terminal status → persist) and differs only in trigger, goal, and result schema.

The shared core loop

CalleClient.start_call ── POST /v1/calls (task + recipient_result_schema)
            │  run_id "call_…"
            ▼
loop: CalleClient.call_status ── GET /v1/calls/{run_id} (every 10s, max 180s)
            │  terminal?
            ▼
persist result ── status · summary · transcript · structured_result

1. Book a callback (outbound)

Volunteer ── "Book a call" form ──> CallbackRequest (booking intent)
                                        │  enqueue
                                        ▼
                                  CallbackCallJob
                                        │
                                        ▼
                          CalleClient.start_call (booking_goal)
                                        │  run_id
                                        ▼
                          loop: call_status ── poll to terminal
                                        │
                                        ▼
                          apply_terminal_result! ──> CallBooking
                                        │
                                        ▼
                          BookAppointmentJob ── GoogleCalendarService.create_event
                                                  (conferenceData.createRequest → hangoutsMeet)
                                        │
                                        ▼
                          Google Meet link + confirmation email
                                        │
                                        ▼
                          CalleeReminderCallJob ── follow-up call

2. Apply over the phone (outbound registration)

Volunteer ── "Apply" form ──> CallbackRequest
                                  │  enqueue
                                  ▼
                            CalleRegistrationJob
                                  │
                                  ▼
                  CalleClient.start_call (REGISTRATION_RESULT_SCHEMA)
                                  │  run_id
                                  ▼
                  loop: call_status ── poll (up to 600s for ~5min call)
                                  │
                        ┌─────────┴─────────┐
                        ▼                   ▼
                   COMPLETED         NO_ANSWER / VOICEMAIL / BUSY
                        │                   │
                        ▼                   ▼
            CalleRegistration.submit!   auto_retry_or_reopen!
                        │
             ┌──────────┴──────────┐
             ▼                     ▼
    VolunteerApplication     CallBooking (if interview slot captured)
                                     │
                                     ▼
                            BookAppointmentJob

3. Inbound hotline (dispatcher → webhook): code-complete, blocked upstream on CALL-E #399

Volunteer calls +1 xxx-xxx-xxx
            │
            ▼
CALL-E dispatcher goal: "apply, book, or read your inbox?"
            │  completed call event
            ▼
Webhook POST /webhooks/calle
            │
            ▼
CalleController#valid_event?  (CALL-E-Event-Id match + optional HMAC)
            │
            ▼
CalleWebhook routes intent
   ├─ read_inbox?    ──> InboundReadInboxJob
   ├─ registration?  ──> InboundRegistrationJob
   └─ bookable?      ──> InboundCallBookingJob ──> CallBooking + BookAppointmentJob

4. Host voice task (outbound)

Host clicks "Approve & call"  (HostTask#voice_dispatchable?)
            │
            ▼
HostTaskVoiceCallJob  (resolve volunteer_phone, assemble voice_goal)
            │
            ▼
CalleClient.start_call  (per-org BYO api_key)
            │  run_id
            ▼
loop: call_status ── poll
            │
   ┌────────┴────────┐
   ▼                 ▼
COMPLETED       NO_ANSWER / VOICEMAIL / error
   │                 │
   ▼                 ▼
apply_voice_result!   reopen_voice_dispatch!
(close task + verify mission goal)   ("Approve & call" returns)

5. Read your inbox (two-call bridge)

Caller picks "read my inbox"
            │
            ▼
Agent captures email + PIN  (no DB access mid-call)
            │
            ▼
InboundReadInboxJob
            │
            ▼
VolunteerInboxService.verify (email + PIN + ANI + progressive lockout)
            │
   ┌────────┴────────┐
   ▼                 ▼
verified        failed / not verified
   │                 │
   ▼                 ▼
unread_messages_for   record "failed": zero content read
   │
   ▼
CalleClient.read_inbox_goal(name, messages)
   │
   ▼
Outbound read-back call (messages read verbatim)

How we built it

  • A Ruby on Rails app with a CalleClient wrapper around the CALL-E Developer API (v0.7.0).
  • Google Calendar API for events, Meet links, and attendees.
  • Per-type calendars (GOOGLE_CAL_ONSITE / ONLINE / PARTNERSHIP) whose recurring weekly "availability" events expand into bookable slots.
  • Background jobs orchestrate the flow: dispatch the call, poll to completion, create the calendar event, add the attendee, send the email.
  • A custom MCP server (mcp/) exposing get_unread_messages and get_application_status tools, PIN-gated through the same identity service.
  • A multi-factor identity gate (caller phone (ANI) + 6-digit PIN + progressive lockout) before any personal data is read.
  • Dry-run by default: we only spend real call credits when we explicitly opt in.

Challenges we ran into

The hard part wasn't the API. It was voice reliability and real-time correctness.

  1. Short answers weren't detected. A simple "yes" triggered a "can you hear me?" loop instead of advancing the conversation.
  2. No tool-calling. CALL-E can't read live availability mid-call, so we snapshot free slots at dispatch and backstop with overlap-aware validation to prevent double-booking.
  3. No inbound result webhook (vendor issue #399), which blocks the phone-in path until support enables it.
  4. Thailand +66 numbers are rejected by the live API, limiting our local audience.
  5. Prompt budget. Long prompts exhausted the agent's turn budget mid-call, so we trimmed to a four-question flow under five minutes.
  6. Timezone discipline: everything is anchored to Asia/Bangkok for a global volunteer base.

Contributing to CALL-E's awesome-phone-call-agents repo added its own hurdles:

  1. Open-sourcing a production slice safely. Our monorepo holds a production database dump and real volunteer PII, so we had to whitelist-copy only the CALL-E files and never stage the whole tree, a genuine risk of leaking data into a public repo.
  2. No Ruby app slot. The repo accepts Python, TypeScript, and web apps, not Ruby on Rails, so we shipped our integration as a portable Agent Skill plus an external reference repo instead of a runnable app.
  3. Strict contribution gates. Every skill must carry SKILL.md plus references/safety.md and references/examples.md, pass validate_repository.py, follow Conventional Commits, and honor safety rules (masked numbers, E.164, explicit intent, no live-by-default, no hidden schedules).

Accomplishments that we're proud of

  • A real, billed call that works end-to-end: the AI introduced itself as OpenmindProjects, captured a structured result, and the appointment landed on Google Calendar with a Meet link and confirmation email.
  • Moving availability from hardcoded windows to per-type Google Calendars as the single source of truth.
  • Overlap-aware slot validation that rejects any slot within 30 minutes of an existing booking.
  • A two-call "read your inbox" bridge plus a reference MCP server that exposes the same inbox tools to host agents.
  • A consolidated vendor report (CSE-1 through CSE-12) that turned our pain points into a clear, actionable ask.
  • Bring-your-own-key billing: each host can attach their own CALL-E API key, encrypted at rest with a masked display, test-before-use validation, and one-click disconnect, so calls bill to the host's own account.

What we learned

  • Voice AI reliability (VAD, barge-in, and TTS quality) is the real frontier; the API integration is the easy 20%.
  • Without tool-calling you need defense-in-depth: snapshot + validation + human review.
  • Dry-run-first is essential when every call costs money.
  • Documenting vendor gaps early turns a blocker into a ticket, not a dead end.

What's next for Hello Volunteer: Goal-Driven Voice Booking

Product roadmap

  • Go live with the inbound hotline once CALL-E enables the result webhook and binds our toll-free number.
  • Add +66 / Southeast Asia support and Thai-language conversations.
  • Move to true dynamic availability (tool-calling / free-busy) when the platform exposes it.
  • SMS and WhatsApp reminders, plus deeper volunteer-lifecycle follow-up in our CRM.

More portable Agent Skills (extending the volunteer lifecycle)

  • Volunteer onboarding calls: walk every confirmed volunteer through next steps, published as a volunteer-onboarding skill.
  • Pre-departure travel checklist: visa, flights, packing, arrival, published as a pre-departure-checklist skill.
  • Weekly onsite check-ins: collect structured feedback, published as a volunteer-wellbeing-checkin skill.
  • Alumni feedback & reviews: post-placement testimonials with consent, published as a testimonial-collection skill.

New vs. existing work

Mindy, our AI volunteer agent, and the platform around it (web booking, calendars, CRM, missions, reviews) were developed during August. CALL-E is the newest layer we added on top of Mindy.

What CALL-E adds:

  • A CalleClient wrapper around the CALL-E Developer API (v0.7.0).
  • The outbound call jobs: callback booking, phone application, and host voice tasks.
  • The inbound hotline dispatcher and its webhook endpoint.
  • The "read your inbox" two-call bridge and its MCP server.
  • A multi-factor identity gate (ANI + PIN + progressive lockout) and dry-run-by-default safety mode.
  • Voice-triggered Google Calendar + Meet booking, where previously only the web form could create events.

Mindy and its web flow already handle the applications, calendars, and approval gates; CALL-E plugs into that same platform so every voice conversation lands in the same records.

Third-party integrations

  • CALL-E Developer API (v0.7.0): used under our developer account and API key, per the platform's terms.
  • Google Calendar API + Google Meet: accessed through the organization's Google Workspace OAuth consent.
  • Gmail (confirmation emails): sent through the same Google Workspace authorization.

All three integrations are used within their respective terms of service and licensing.

Source code & docs


Built With

Share this project:

Updates

Submission history