Send an annual birthday reminder through IllyVoIP from your own scheduler and an explicit recipient list. This guide focuses on date-only recurrence, leap-day policy and preventing repeat sends within the same year. It does not describe an automatic birthday feature in the Portal.

1. Prepare a minimal recipient record
The SMS API sends when your worker calls it. Scheduling, business-event checks and suppression belong to your application; this guide does not add undocumented scheduling fields to IllyVoIP.
You need an IllyVoIP account permitted to use the relevant API, sufficient balance for paid operations and an API key. In the Portal, open Account settings → Developer tools and follow its verification prompts. Keep the key in your server’s secret configuration as ILLYVOIP_API_KEY, outside source control and shared documents. Read-only catalog and reporting requests are different from paid speech generation or SMS submission.
Keep a stable customer ID, approved phone number, month/day, timezone, enabled preference and any consent record your business needs. A birth year is not necessary just to schedule an annual greeting. Do not infer someone’s age or birthday from other data.
Use an explicit IANA timezone and a chosen local delivery time. Validate dates when the record is created. Reject impossible values such as April 31. Keep invalid or missing values in a review list instead of making a guess.
2. Choose the February 29 policy
Decide whether a February 29 birthday is observed on February 28, March 1 or skipped in a non-leap year. Document that choice and keep it consistent. The following pure Python function handles the calendar decision only; it does not send SMS.
from datetime import date
def occurrence(month, day, year, leap_policy="skip"):
if leap_policy not in {"skip", "feb28", "mar1"}:
raise ValueError("Unknown leap-day policy")
date(2000, month, day) # Validate using a leap year.
try:
return date(year, month, day)
except ValueError:
if (month, day) != (2, 29):
raise
if leap_policy == "skip":
return None
return date(year, 2, 28) if leap_policy == "feb28" else date(year, 3, 1)
Resolve the occurrence and chosen local sending time to UTC using a timezone-aware scheduler. If that time is ambiguous or nonexistent at a daylight-saving change, apply an explicit tested policy rather than silently shifting it.
3. Make one durable job per annual occurrence
Use a unique identity based on the stable recipient ID, campaign/purpose and occurrence year. The uniqueness rule must live in the database, not only in memory. Do not use a random new key each time a daily scheduler sees the same birthday.
Store the resolved due instant, source revision, frozen destination, allowed sender, message text and idempotency key. If a phone number or preference changes, review any unsent job before dispatch. A newly imported duplicate customer record can bypass recipient-ID uniqueness, so normalize destinations and review duplicate people before enabling the list.
4. Recheck eligibility at the sending window
- Claim only an eligible due job using an atomic database update.
- Read the current enabled preference, source revision and destination.
- Apply the web suppression list and local sending-window policy.
- Skip a stale job after the chosen lateness cutoff; do not send a pile of old birthday messages after an outage.
- Confirm that this purpose/year has no accepted or uncertain attempt before passing the frozen request to the worker.
A changed message template does not authorize another greeting in the same year. Treat a correction or another business purpose as a separate reviewed decision.
Submit through the existing SMS worker
Use the durable outbox from the authenticated website-form SMS guide or your existing equivalent. Store a unique business-event identity, normalized recipient, allowed sender, reviewed text and stable idempotency key before dispatch. The HTTP request uses this shape:
POST https://api.illyvoip.com/api/v1/sms/messages
X-Api-Key: YOUR_SERVER_SIDE_KEY
Idempotency-Key: THE_STORED_KEY_FOR_THIS_MESSAGE
Content-Type: application/json
{"from":"YOUR_ALLOWED_SENDER",
"to":["AUTHORIZED_INTERNATIONAL_NUMBER"],
"message":"THE_FROZEN_REVIEWED_TEXT"}These placeholders are not working credentials or a destination to test. Your application performs the business checks before this immediate request. Use an atomic claim so two workers cannot send the same job. Persist an attempting/uncertain state before network I/O, then save accepted IDs and rejected recipients. Do not treat HTTP success as delivery; follow the status endpoint or a verified delivery webhook.
Preserve the exact input, local job identity and response before moving to the next step. A timeout or lost response is an uncertain outcome, not permission to submit the same paid operation again under a new name. Recover from a saved successful response when possible; otherwise reconcile the original attempt. API duplicate-charge protection does not replace your application’s operation history.
Test the annual edge cases
Test February 29 in leap and non-leap years under all three policies; month/year rollover; a disabled recipient; an edited phone number; duplicate scheduler runs; and two workers claiming the same annual job. Test a timeout after possible acceptance and verify that rerunning the scheduler does not send again.
Use synthetic dates and dummy recipients for local checks. For a real test, use a consenting number you control, check price and segments, and inspect the eventual delivery state. Keep the business preference record separate from whether one individual SMS happened to deliver.
Related guides and reference
Python calendar dates. IllyVoIP API reference. Reviewed October 11, 2026. Examples are instructional: local checks do not establish a completed paid request, live third-party integration or production PBX acceptance.