Communications
This page documents the current outbound communication behavior used by the API.
Invoice Email Provider
Invoice emails and patient import invitation emails are sent through Resend.
Runtime wiring:
cmd/main.gocreatesservices.NewResendEmailService(cfg).- The router stores it as
EmailSender. - Invoice send flows call
rctx.EmailSender.SendInvoice(...). - Patient import invitation flows call
SendPatientInvitation(...)when the configured email sender supports it. services/resend.gosends aPOSTrequest to the Resend/emailsAPI.
Configuration:
| Environment Variable | Required | Default | Purpose |
|---|---|---|---|
RESEND_API_KEY | Yes | - | Bearer token used for Resend API calls |
RESEND_FROM_EMAIL | Yes | - | Sender email. Plain addresses are normalized to Aivida <email> |
RESEND_API_BASE | No | https://api.resend.com | Resend API base URL |
Invoice Email Flow
Invoice emails are generated with a PDF attachment and sent through Resend.
Manual invoice email flow:
- Call
invoices/send-email. - Backend loads the invoice, organization, and patient.
- Backend renders the invoice PDF.
- Backend calls
EmailSender.SendInvoice. - Resend sends the email with the PDF attachment.
- Backend updates
emailedAton the invoice after a successful send.
Automatic invoice email flow after successful payment:
- The payment success endpoint creates or updates the payment.
- Backend calls
payments/common.SendInvoiceForPayment. - Backend creates a paid invoice for the payment source when needed.
- Backend calls
invoices/send-email. - The invoice is delivered through Resend.
Automatic invoice sending is used by these current success flows:
| Flow | Trigger |
|---|---|
| Appointment payment success | appointments/mark-payment-success |
| Appointment create with completed payment | appointments/create |
| Provider order payment success | providerOrders/markPaymentSuccess unless skipInvoice is true |
| Cart checkout payment success | cart/payment-success unless skipInvoice is true |
| Existing invoice payment | Payment source type invoice |
Patient Import Invitation Flow
Patient import invitation emails are sent after patientProfiles/importRecords successfully creates or updates an imported patient profile with an email address.
Flow:
- Backend validates that the
doctorIdbelongs to theorganizationId. - Backend creates or updates each imported patient profile.
- For each successfully created or updated patient with an email address, backend attempts one invitation email per unique email in the import batch.
- The email subject says that the importing doctor is available on Aivida.
- The email body tells the patient that the doctor added their details to Aivida and is available there for healthcare needs.
This email is best-effort. If the email sender is not configured, does not support patient invitations, or Resend returns an error, the import response is still returned and the failure is logged.
Module-Based Notifications
Use the notifications namespace for in-app notification records and module-based communication routing. The frontend can send module to group notifications by product area, for example appointments, providerOrders, labOrders, billing, or hrmPayroll.
module is a frontend-friendly alias for relatedEntityType. If both module and relatedEntityType are sent, relatedEntityType wins.
Supported Notification Channels
| Channel | Purpose | Notes |
|---|---|---|
push | Mobile push notification | Store the notification with channels: ["push"]. Devices are managed through pushDevices using Expo push tokens. |
sms | SMS communication | Store the notification with channels: ["sms"]. Use deliveryState, deliveryError, and related entity fields for provider tracking/audit. |
email | Email communication | For invoice and invitation emails, use the dedicated email flows documented above. |
in_app | App notification center | Store the row for app inbox/list views. |
The notifications.create API records the communication intent and delivery metadata. Push/SMS delivery workers or provider integrations can use channels, recipientStackUserId, module, relatedEntityId, and dedupeKey to process and update delivery state.
Create Appointment Push Notification
curl -X POST https://api.aivida.in/api \
-H "Content-Type: application/json" \
-H "x-stack-access-token: <access_token_optional>" \
-d '{
"namespace": "notifications",
"apiName": "create",
"data": {
"organizationId": "org_123",
"recipientStackUserId": "patient_stack_user_123",
"recipientRole": "patient",
"type": "appointment_reminder",
"title": "Appointment reminder",
"message": "Your appointment starts in 15 minutes.",
"module": "appointments",
"relatedEntityId": "appointment_123",
"channels": ["push", "in_app"],
"navigationTarget": "appointment_details",
"navigationParams": {
"appointmentId": "appointment_123"
},
"deliveryState": "PENDING",
"dedupeKey": "appointment_123:reminder_15m"
}
}'Create Appointment SMS Notification
curl -X POST https://api.aivida.in/api \
-H "Content-Type: application/json" \
-H "x-stack-access-token: <access_token_optional>" \
-d '{
"namespace": "notifications",
"apiName": "create",
"data": {
"organizationId": "org_123",
"recipientStackUserId": "patient_stack_user_123",
"recipientRole": "patient",
"type": "appointment_sms_reminder",
"title": "Appointment SMS reminder",
"message": "Reminder: your Aivida appointment is scheduled for 10:30 AM.",
"module": "appointments",
"relatedEntityId": "appointment_123",
"channels": ["sms"],
"deliveryState": "PENDING",
"dedupeKey": "appointment_123:sms_reminder"
}
}'List Notifications by Module
Use module when the app needs a module-specific notification center, for example only appointment notifications.
curl -X POST https://api.aivida.in/api \
-H "Content-Type: application/json" \
-H "x-stack-access-token: <access_token_optional>" \
-d '{
"namespace": "notifications",
"apiName": "listByOrganization",
"data": {
"organizationId": "org_123",
"module": "appointments",
"page": 1,
"pageSize": 20
}
}'Without module, notifications.listByOrganization returns all organization notifications.
Appointment Module Communication
Appointment communications are sent from the appointment APIs and the scheduled reminder worker. All appointment push notification records use relatedEntityType: "appointment" and navigate to appointment_detail.
Current Appointment Scenarios
| Scenario | Trigger | Patient push / in-app | Patient SMS | Patient email | Doctor push / in-app | Doctor email |
|---|---|---|---|---|---|---|
Appointment created by appointments/create | Appointment row is created | Only when the booking is already paid, through the confirmation flow | Only when the booking is already paid, through the confirmation flow | Invoice email only if payment/invoice flow succeeds | Sent immediately: Appointment created | Sent if Resend is configured and doctor email exists |
| Appointment created as scheduled without paid booking | appointments/create with status scheduled and not paid | A scheduled appointment_booked notification is queued for immediate processing | A scheduled appointment_booked SMS is queued for immediate processing | No appointment email | Sent immediately: Appointment created | Sent if Resend is configured and doctor email exists |
| Pending appointment created | appointments/create-pending | No current automatic push | No current automatic SMS | No current automatic email | No current automatic doctor notification | No current automatic doctor email |
| Doctor-created appointment | appointments/create-by-doctor | No current automatic push | No current automatic SMS | No current automatic email | No current automatic notification | No current automatic email |
| Payment marked successful | appointments/mark-payment-success, payment success handler, or paid appointments/create | Sent to patient: Appointment booked | Sent to patient using appointment confirmation DLT template | Invoice email may be sent by invoice flow; no separate appointment confirmation email | No extra doctor notification from this flow | No extra doctor email from this flow |
Status changed to confirmed | appointments/update and previous status was not confirmed | No current automatic push | Sent to patient using appointment confirmation DLT template | No current automatic email | No current automatic doctor notification | No current automatic doctor email |
Status changed to cancelled | appointments/update and previous status was not cancelled | No current automatic patient push | Sent to patient using appointment cancellation DLT template | No current automatic patient email | Sent only when the actor is the patient/customer or matches the appointment patient | Sent to doctor if Resend is configured and doctor email exists, same actor rule as push |
| Appointment rescheduled with dedicated API | appointments/reschedule and date/time/duration changed | Sent to patient: Appointment rescheduled | Sent to patient using appointment rescheduled DLT template | Sent to patient if Resend is configured and patient email exists | Sent to doctor: Appointment rescheduled | Sent to doctor if Resend is configured and doctor email exists |
| Appointment date/time changed through generic update | appointments/update with changed date/time/duration | No immediate reschedule push from this endpoint | No immediate reschedule SMS from this endpoint | No immediate reschedule email from this endpoint | No immediate doctor reschedule notification from this endpoint | No immediate doctor email from this endpoint |
| Consultation started | appointments/update status changes to in_progress, or appointments/trigger-call-join-notification | Sent to patient: Consultation started | Sent to patient using consultation-started template | Sent to patient if Resend is configured and patient email exists | trigger-call-join-notification also sends doctor Patient joined consultation | Sent to doctor if Resend is configured and doctor email exists |
| Patient joined call | appointments/patient-joined-call | No patient notification | No patient SMS | No patient email | Sent to doctor: Patient joined consultation | Sent to doctor if Resend is configured and doctor email exists |
Appointment Reminder Schedule
The reminder service schedules these jobs when an appointment has a valid patient stackUserId, valid date/time, and is not cancelled:
| Reminder | Recipient | Scheduled time | Channels |
|---|---|---|---|
appointment_booked | Patient | Immediately, only when ScheduleForAppointment is used | Push/in-app and SMS |
appointment_reminder_24h | Patient | 24 hours before appointment start | Push/in-app and SMS |
appointment_reminder_1h | Patient | 1 hour before appointment start | Push/in-app and SMS |
appointment_reminder_15m | Patient | 15 minutes before appointment start | Push/in-app and SMS |
doctor_appointment_starting_15m | Doctor | 15 minutes before appointment start | Push/in-app and email if Resend is configured |
doctor_patient_waiting_10m | Doctor | 10 minutes after patient waiting is scheduled | Push/in-app and email if Resend is configured |
Reminder timing uses APPOINTMENT_TIMEZONE; default is Asia/Kolkata. If a reminder time is already in the past when the job is created, it is scheduled for the current time. The scheduler polls once per minute, claims up to 100 due notifications per poll, and retries failed scheduled notifications up to 3 times.
When an appointment is cancelled, pending scheduled reminders for that appointment are cancelled. When appointment date, time, or duration changes, pending reminders are cancelled and recreated for the new appointment time.
Appointment SMS Templates
| Event | Template key | Default DLT ID | Body |
|---|---|---|---|
| Appointment confirmation / booked | appointment-confirmation | 1007098381514317785 | Dear {name}, your appointment is confirmed for {datetime}. Please arrive on time. - AIVIDA |
| Appointment cancellation | appointment-cancellation | 1007514674555560874 | Dear {name}, your appointment scheduled for {datetime} has been cancelled. Please contact support for assistance. - AIVIDA |
| Appointment rescheduled | appointment-rescheduled | 1007897657973836151 | Dear {name}, your appointment has been rescheduled to {datetime}. Please visit at the updated time. - AIVIDA |
| Appointment reminder | appointment-reminder | 1007702540941982595 | Dear {name}, this is a reminder for your appointment scheduled on {datetime}. Please be available on time. - AIVIDA |
| Consultation started | consultation_started | AIRTEL_CONSULTATION_STARTED_DLT_TEMPLATE_ID or AIRTEL_DLT_TEMPLATE_ID | Dear {name}, your consultation has been started by the doctor. Please join now. - AIVIDA |
SMS is sent through Airtel when AIRTEL_CUSTOMER_ID, AIRTEL_ENTITY_ID, AIRTEL_SOURCE_ADDRESS, an applicable DLT template ID, and a valid Indian mobile number are available. AIRTEL_AUTHORIZATION or AIRTEL_AUTH_TOKEN is used when configured.
Appointment Push and Email Rules
Push delivery uses active push devices from pushDevices plus the patient’s saved Expo token where available. If no push token exists, the notification row is still created and delivery is marked skipped.
Push providers are Expo and Firebase through the default push provider. Email uses Resend and is best-effort; when Resend credentials or recipient email are missing, the backend skips email and logs the reason.
Current appointment email coverage is:
| Recipient | Email is sent for |
|---|---|
| Patient | Invoice after paid appointment, appointment rescheduled, consultation started |
| Doctor | Appointment created, appointment rescheduled, patient cancelled appointment, appointment starting soon, patient joined consultation, patient waiting |
There is currently no separate patient email for plain appointment created, confirmed status update, or cancelled status update.
Update Delivery State
After a push or SMS provider attempt, update the notification row with the delivery outcome.
curl -X POST https://api.aivida.in/api \
-H "Content-Type: application/json" \
-H "x-stack-access-token: <access_token_optional>" \
-d '{
"namespace": "notifications",
"apiName": "update",
"data": {
"id": "notification_123",
"organizationId": "org_123",
"module": "appointments",
"deliveryState": "SENT",
"pushTicketIds": ["expo_ticket_123"]
}
}'For failed SMS or push attempts, send deliveryState: "FAILED" and put the provider error in deliveryError.
Failure Behavior
Payment confirmation is not rolled back only because invoice email delivery fails in the cart flow. Other payment paths log invoice delivery failures and continue according to the endpoint flow. Patient import invitation delivery failures are logged and do not fail the import. Resend configuration problems return errors such as missing API key, missing from email, empty recipient email, or empty PDF attachment.
Related APIs
invoices/send-email: sends an existing invoice email.providerOrders/markPaymentSuccess: confirms provider-order payment and can trigger invoice email.appointments/markPaymentSuccess: confirms appointment payment and can trigger invoice email.cart/payment-success: confirms cart checkout payment and can trigger invoice email.patientProfiles/importRecords: imports patient records and can trigger patient invitation email.notifications/create: creates in-app, push, or SMS notification records with module grouping.notifications/listByOrganization: lists all organization notifications or filters bymodule.notifications/update: updates delivery state, push tickets, read/open state, or provider errors.pushDevices/*: manages Expo push tokens used by push notification delivery.