Thank first-time donors with a voice message over iMessage
ZeroSettle lets a nonprofit text each first-time donor who opted in a short thank-you from the executive director and, once the donor replies, send a voice message in that director’s voice, which arrives as a native iMessage voice bubble, followed by a photo of the program the gift supports. On the $49-a-month Pro plan, one number reaches 5 new donors a day, about 150 a month. Replies land in the ZeroSettle console, and later impact updates in the same thread never count against that limit.
Key takeaways
- What it is: a short thank-you text from your executive director and, after the donor replies, a voice thank-you and a program photo with the donor’s name in the caption, sent from your organization’s own iMessage number by a daily script and a webhook.
- Who it fits: small and mid-size nonprofits whose donors tick a “text me a thank-you and updates” box on the donation form.
- Price: Pro is $49/month for one number. Growth, for several numbers working as one, is from $599/mo. No per-message fees.
- Main limit: 5 new donors a day per number on Pro, about 150 a month, and voice and photos only after the donor replies. Replies and later updates to a donor you already thanked don’t count against it.
- What it does not do: reach donors on Android (there is no SMS fallback), or replace the written acknowledgment IRS rules call for on gifts of $250 or more.
Who uses iMessage for donor thank-yous?
This workflow is for development and executive directors at small and mid-size nonprofits who want each first-time donor who opted in to texts to hear a personal thank-you the morning after the gift. ZeroSettle is an iMessage API: a real phone number on real iMessage, running on Apple hardware we operate for you, that your code texts customers from and hears back on through a bearer token and signed webhooks.
The problem is the second gift, and the thank-you is where it starts. A first-time donor may get only an emailed receipt, and a thank-you call takes staff time and can end in voicemail. A 30-second message in the executive director’s voice needs no one to pick up. Year-end volume and Android donors are covered under when ZeroSettle is not a fit.
How iMessage for donor thank-yous works, step by step
A donor ticks a box, a script runs the next morning with a short thank-you text, and when the donor replies they get two more messages from your number: the executive director’s voice, then a photo of the work the gift funds.
- Add an optional, unticked “Text me a thank-you and updates” checkbox beside the mobile number on your donation form, and store the consent with the gift.
- The executive director records a thank-you on their own phone, one per program, under a minute, opening with their name and the organization’s; the docs say two or three sentences land best. ZeroSettle accepts m4a, mp3, wav, aiff, caf, flac, aac, mp4 and amr files up to 6 MB, and voice messages for language-learning apps covers audio prep.
- Each morning, after 8 am in your donors’ time zones, a script takes yesterday’s opted-in first-time donors and calls
POST https://portal.zerosettle.io/api/imessage/sendwith a short, link-free text from the executive director that invites a reply. ZeroSettle sends photos and voice messages only to contacts who have replied (403 attachment_before_replyotherwise), so the recording waits for that reply. - When the donor’s reply arrives on your webhook, your server sends the recording with
audio_b64. The donor gets Messages’ native voice bubble with a waveform, a play button and a transcript their iPhone generates. - A second send carries
image_b64and a caption with the donor’s first name, because a voice message can’t carry a caption. - Once you set your name and logo in the console, they travel with the first message, so the donor sees “Lanternfield Food Bank shared a name and photo with you”.
- Replies land in the ZeroSettle console, where staff answer from the same number. Months later, an impact update in the same thread is an ongoing conversation, with no new-contact limit.
The same leader-to-newcomer pattern drives founder-led SaaS onboarding over iMessage; here the recording does the personal part.
Example conversation: a donor thank-you
This thread shows the thank-you text, the donor’s reply, the voice message and photo, a tapback from the donor and an impact update three months later, all on one number.
- Wed 10:04 AM
- Food bank: Hi Mark, it’s Angela, Executive Director at Lanternfield Food Bank. Thank you for your first gift! Can I send you a 30-second thank-you?
- Donor: oh nice, sure
- Food bank: Voice message, 0:31 (Angela, Executive Director)
- Food bank: Photo: volunteers stocking pantry shelves
Your gift helps stock these shelves. Thank you, Mark.❤️ - Wed, Jan 13, 11:00 AM
- Food bank: Mark, quick update: we served 1,240 families this quarter, our most ever.
Setup: a morning script with voice and photo sends
The integration is one daily script that texts each opted-in first-time donor, plus a webhook handler that sends the voice message, then the captioned photo, when that donor replies, all through POST /api/imessage/send. Request shapes follow the Attachments section of the ZeroSettle docs, and what an iMessage API can send sums up photo and voice limits.
# thank_donors.py: run each morning: python3 thank_donors.py new_donors.csv
# Your webhook handler calls send_thanks() when a donor's first reply arrives.
import base64, csv, os, sys, time
import requests
BASE = "https://portal.zerosettle.io"
TOKEN = os.environ["ZEROSETTLE_TOKEN"]
FIRST = ("Hi {name}, it's Angela, Executive Director at Lanternfield Food Bank. "
"Thank you for your first gift! Can I send you a 30-second thank-you?")
PROGRAMS = {"pantry": ("thanks-pantry.m4a", "shelves.jpg",
"Your gift helps stock these shelves. Thank you, {name}.")}
RETRY = ("new_contact_limit", "line_busy", "phone_offline", "phone_timeout")
SKIP = ("recipient_opted_out", "not_on_imessage", "send_failed",
"awaiting_reply", "attachment_before_reply") # this donor only
class DailyCap(Exception):
pass
class Skip(Exception):
pass
def b64(path):
with open(path, "rb") as f:
return base64.b64encode(f.read()).decode()
def send(body):
for attempt in range(8):
r = requests.post(f"{BASE}/api/imessage/send", json=body,
headers={"Authorization": f"Bearer {TOKEN}"}, timeout=120)
if r.ok:
return r.json()
err = r.json()["error"]
if err["code"] == "new_contact_limit" and err.get("window") == "day":
raise DailyCap()
if err["code"] in SKIP:
raise Skip(f"{err['code']}: {err['message']}")
if err["code"] not in RETRY:
break # bad token, unpaid, bad input
time.sleep(err.get("retry_after_s", 2 * (attempt + 1)))
sys.exit(f"stopping: {r.status_code} {err['code']}: {err['message']}")
def send_thanks(phone, first_name, program): # after the donor replies
audio, photo, caption = PROGRAMS[program]
send({"to": phone, "audio_b64": b64(audio), "audio_format": "m4a"})
send({"to": phone, "image_b64": b64(photo), "image_format": "jpg",
"text": caption.format(name=first_name)})
if __name__ == "__main__":
done = set(open("thanked.txt").read().split()) if os.path.exists("thanked.txt") else set()
for d in csv.DictReader(open(sys.argv[1])): # phone, first_name, program
if d["phone"] in done:
continue
try:
send({"to": d["phone"], "text": FIRST.format(name=d["first_name"])})
except DailyCap:
break # the rest go out tomorrow
except Skip as e:
print(d["phone"], e) # opted out or not on iMessage: email instead
with open("thanked.txt", "a") as f:
f.write(d["phone"] + "\n")
Only the morning text can hit the new-contact limit; by the time the voice message and photo go out, the donor has replied. A refusal about one donor is logged and skipped; anything else, like a bad token, stops the run with nobody marked thanked. Send the recording to your own number once before the first run. Register a webhook and verify each message.received before calling send_thanks or logging the reply against the donor record: the X-ZeroSettle-Signature header carries an HMAC-SHA256 of "<t>." plus the raw body, checked with a constant-time compare as in the webhook steps in the docs. Keep the number “Managed by API”: while the hosted AI agent manages it, your script’s sends are refused with 403 managed_by_zerosettle.
Limits and consent for donor thank-yous
On Pro, one number reaches 5 new donors a day, about 150 a month. Replies are never limited, and later messages to a donor you already thanked never count as new contacts, though every plan caps unanswered messages at 2.
| Limit | Pro | Growth |
|---|---|---|
| New donors per day (3 AM to 3 AM Eastern) | 5 | 50 |
| New donors per rolling 15 minutes | — | 3 |
| Unanswered messages to one donor | 2, then 403 awaiting_reply | 2, then 403 awaiting_reply |
| Voice messages and photos | Only after the donor replies | Only after the donor replies |
| Replies and later messages count as new contacts | No | No |
Source: ZeroSettle iMessage API docs, “Errors, limits, and compliance”.
The year-end math
- 400 first-time gifts on December 31: at 5 a day on Pro, the last donor hears from you 80 days later. On Growth each number allows 50 a day, so three numbers finish in three days. Or thank the surge by email.
- Donors who text first are never limited. A prefilled
sms:link on the donation confirmation page lets donors start the thread (on Android it opens the SMS app, so keep email for those donors); see how to get customers to text you first. - Quiet donors: a donor who never answers the thank-you text gets no voice message or photo. The thank-you is one unanswered message and the next impact update is the second; a third waits for a reply.
Consent comes first
Text only donors who ticked the box, and keep the record; the iMessage TCPA consent guide covers what to store. Lead with thank-yous and impact. Keep fundraising asks rare, and send them only between 8 am and 9 pm in the donor’s time zone. This isn’t legal advice; check consent rules with counsel.
A text with the word STOP, or a clear request like “take me off your list”, opts the donor out across every number on your account, including a donor’s “I’ll stop by Saturday.” Sends to them return 403 recipient_opted_out, and only START opts them back in.
When ZeroSettle is not a fit for donor thank-yous
ZeroSettle is the wrong tool for donor thank-yous in these cases:
- Hundreds of new donors a day on one Pro number, as at year-end. Plan Growth numbers or email for the surge.
- Donors who didn’t opt in to texts. A gift is not consent to be texted, and ZeroSettle suspends lines used for unsolicited messaging.
- Donors on Android or another phone without iMessage. There is no SMS fallback, so they get nothing; thank them by email.
- Fundraising appeals to your whole list. ZeroSettle’s terms prohibit high-volume marketing.
- The message as the tax receipt. Under IRS rules, a contribution of $250 or more is substantiated by a written acknowledgment with your organization’s name, the amount of a cash gift (or a description of a non-cash one) and a statement about goods or services. A voice message is not one, so keep sending receipts. This isn’t tax advice.
How much does iMessage for donor thank-yous cost?
Donor thank-yous cost the plan price: ZeroSettle charges no per-message fees, and messages in both directions are unlimited (counted).
| Plan | Price | Fits |
|---|---|---|
| Pro | $49/month | One number, 5 new donors a day, unlimited messages with people who message you first, self-serve, no setup fee |
| Growth | From $599/mo | Several numbers working as one, set up after a demo |
Both are billed monthly with a 30-day money-back guarantee. See ZeroSettle pricing, more iMessage use cases, or get a number.
Frequently asked questions
What if a donor replies with a voice message or a photo?
It arrives with the file, in the console and as message.received on your webhook. ZeroSettle converts a voice reply to .m4a with its length in duration_ms, and a HEIC photo to JPEG, so staff can play or open either in a browser.
Can the donor replay the message later?
Only if they keep it. Apple says a recipient can play an audio message any time after it arrives, but must tap Keep within 2 minutes of listening to save it, unless they set audio messages never to expire. Your copy stays in ZeroSettle: GET /api/imessage/messages/{id}/audio returns the bytes you sent.
Can we ask for another gift by text?
Only if their consent covers it. ZeroSettle’s docs define consent as a number given for this purpose, with a record you can show, and note that US marketing texts need written consent. A box offering “a thank-you and updates” may not cover appeals, so name them in the checkbox if you plan to ask. How to write a first iMessage that gets replies covers wording. This isn’t legal advice.
Sources
- ZeroSettle iMessage API docs: “Send a message”, “Name and photo”, “Attachments”, “Webhooks, step by step” and “Errors, limits, and compliance”. Checked October 8, 2026.
- ZeroSettle iMessage API Terms of Service, section 13 (opt-in consent and prohibited uses). Last modified September 28, 2026.
- Send and receive audio messages in Messages on iPhone, Apple Support. Checked October 8, 2026.
- Charitable contributions: Written acknowledgments, Internal Revenue Service. Checked October 8, 2026.