Skip to content

Communication

SeminaryERP ships a first-class multi-channel communication system. Every message sent through the system — email, SMS, WhatsApp, Telegram, or the portal In-App inbox — is recorded in a single Communication Log ledger, giving staff a complete audit trail and a CRM-style view of every conversation with each person.

Key concepts

  • Person — the identity spine (see Person). Every stakeholder has exactly one Person record with a stable PERS-##### id. Email and phone are contact addresses, not identity keys: changing them never breaks links to other records.
  • Communication Channel — the medium (Email, SMS, WhatsApp, Telegram, In-App). Seeded automatically; not user-configurable.
  • Channel Provider Account — a configured instance of a channel: your Mailgun account, your Twilio number, your Telegram bot. One channel can have multiple provider accounts (e.g., country-specific SMS numbers).
  • Communication Template — reusable message blueprints with per-channel, per-language versions rendered as Jinja2.
  • Communication Trigger — a declarative rule that fires a template when a document field reaches a configured state — no custom code required.
  • Communication Log — the immutable ledger row created for every outbound or inbound message. Status moves forward only: Queued → Sending → Sent → Delivered → Read.

Person

The Person doctype is the identity spine for all stakeholders — students, applicants, instructors, alumni, and any external party. It replaces email as the primary key: two different email addresses for the same human resolve to a single PERS-##### record.

What a Person contains

SectionFields
IdentityFirst, middle, last name; language; country
ContactPrimary email, primary mobile (mirrors into Channel Addresses)
Channel AddressesPer-channel reachability rows (Email, SMS, Telegram chat id, …) with scope, primary flag, verification status
ConsentsPer-category opt-in/out per channel
LinksUser (login), Customer (financial), linked role records

Role records (Student, Instructor, Student Applicant, Alumni Profile) carry a person Link field. Their contact fields are read-only mirrors hydrated from the Person — editing them is done on the Person form, not on the role record.

CRM Timeline

Opening a Person on Desk shows a Conversation tab listing every Communication Log sent to or received from that person, sorted newest-first. Each entry shows the channel icon, direction arrow, status pill, subject (clickable), timestamp, and the triggering reference document. Staff can compose a new message directly from the Person form using the Email, SMS, or In-App buttons.

How Persons are created

Persons are created automatically at onboarding:

  • Portal / webform path — when a Student Applicant is created, ensure_person() runs and mints a Person (or finds the existing one by email or User link).
  • Staff path — creating an Instructor or User triggers the same function; identity data lifts from the User record.

Staff can also create Persons manually on Desk for external parties (recommenders, donors) who are not system users.

Communication Channels

Seven channels are seeded at install: Email, SMS, WhatsApp, Telegram, In-App, Print, and Voice. Channels are reference data — they are not edited by staff.

Channel Provider Accounts

A Channel Provider Account is what connects a channel to a real service. Navigate to Communication > Channel Provider Account to configure one.

FieldDescription
ChannelWhich channel this account covers (Email, Telegram, …)
ProviderThe adapter that carries it — a dropdown: frappe-email, in-app, telegram, twilio
CountryOptional — routes messages to this account for that country first
Hourly LimitMax outbound messages per hour (0 = unlimited)
CredentialsTyped fields that appear based on the provider (see below); secrets are stored encrypted
Extra Settings (JSON)Advanced/optional — extra raw JSON merged under the typed fields. Secrets belong in the encrypted fields, not here

When you pick a Provider, the matching credential fields appear and the required ones are enforced on save. Auth tokens and bot tokens are Password fields — encrypted at rest, never shown back in plain text.

Two accounts are seeded:

  • Default Email — uses the built-in Frappe email stack (frappe-email); no extra credentials needed.
  • Portal Inbox — the In-App channel (in-app); messages land instantly in the portal inbox.

Adding a Telegram account

  1. Create a bot with @BotFather and copy the token.
  2. Create a Channel Provider Account:
    • Channel: Telegram
    • Provider: telegram
    • Bot Token — the token from @BotFather (encrypted). This is the only field you must fill.
  3. Save. On save the system does the rest automatically: it generates the encrypted Webhook Secret, fetches the Bot Username, and — if the site is publicly reachable over HTTPS — registers the webhook with Telegram.
  4. If the site isn't public yet (e.g. local dev), click Register Telegram Webhook on the form and supply a public HTTPS tunnel URL (e.g. an ngrok/cloudflared address). Telegram only accepts public HTTPS webhooks. The same button lets you re-register at any time.
  5. Recipients connect from their portal Preferences page: the Connect Telegram link opens a signed deep-link that registers their chat id on their Person automatically. Bots cannot initiate contact, so this connect flow is required before any Telegram message can be delivered to that person.

The bench ... execute seminary.seminary.telegram_adapter.setup_webhook command still works as a scripted fallback, but the button (and auto-registration on save) is the normal path.

Adding a Twilio account (SMS or WhatsApp)

One adapter (twilio) serves both SMS and WhatsApp — create one account per channel.

  1. In the Twilio console, copy the Account SID and Auth Token, and provision a sending number (an SMS number, or a WhatsApp-enabled sender).
  2. Create a Channel Provider Account:
    • Channel: SMS (or WhatsApp)
    • Provider: twilio
    • Twilio Account SID — starts with AC.
    • Twilio Auth Token — encrypted.
    • From Number — E.164, e.g. +15551234567. For WhatsApp use the WhatsApp-enabled sender (the sandbox is +14155238886); the whatsapp: prefix is added automatically from the Channel. Or leave it blank and set a Messaging Service SID (MG...) instead.
  3. Delivery receipts are automatic — each message sets a StatusCallback pointing at the shared webhook, so Twilio advances the log to Delivered or Failed.
  4. Inbound replies: in the Twilio console, set the number's "A message comes in" webhook to https://<your-site>/api/method/seminary.seminary.comms.webhook?account=<account name>. Both status callbacks and inbound messages are verified against Twilio's X-Twilio-Signature.

Recipients are reachable once their Person has a phone number on the matching channel (the primary mobile is mirrored to the SMS channel automatically; WhatsApp addresses are added per Person).

Adding a Twilio Voice account

Voice uses the same Twilio credentials as SMS — it just places a call that reads the message aloud (text-to-speech). Create a separate account:

  • Channel: Voice
  • Provider: twilio
  • Twilio Account SID / Twilio Auth Token — as above.
  • From Number — a voice-capable Twilio number (Messaging Service SIDs don't apply to calls).

Voice reuses the recipient's phone number — if a Person has no dedicated Voice address, the system falls back to their SMS number (their primary mobile). The call's outcome (answered / busy / no-answer) flows back through the same webhook.

Print

Print is built in and seeded out of the box (the Print Spool account, provider print) — no credentials. Sending on the Print channel renders the message to a PDF attached to the Communication Log, ready for the office to print and mail. Like the portal inbox, it "delivers" immediately at queue time; there's no address to resolve, so every recipient is reachable. Print uses the full rich Message (not the Short Message).

Communication Templates

Navigate to Communication > Communication Template to create or edit templates.

Each template has one or more versions, each scoped to a channel and optionally a language. When a message is sent, the engine picks the version matching the recipient's preferred language and the target channel, falling back to the default language version.

Template bodies and subjects are Jinja2. The render context always includes:

VariableContents
personThe recipient's Person document
docThe reference document (if reference_doctype / reference_name were passed)
Any extrasKey/value pairs passed by the caller

Seven templates are seeded: waitlist promotion (student and registrar variants), waitlist closed, CEI payment threshold, late-grades nag, few academic terms warning, and recommendation request.

Categories

Every template carries a category that controls consent and routing:

CategoryConsent rule
EmergencyAlways delivered; bypasses throttle and consent
TransactionalDelivered unless the person has explicitly opted out
PromotionalDelivered only if the person has opted in

Communication Triggers

Triggers let staff configure automatic notifications without writing code. Navigate to Communication > Communication Trigger.

FieldDescription
Reference DoctypeThe doctype to watch (e.g., Withdrawal Request)
Trigger OnSave, Insert, Submit, or Cancel
TemplateThe Communication Template to render
Once Per DocumentSend at most once per document name (idempotent re-runs)
ConditionsAND-ed field-match rules evaluated against the saved document
RecipientsWho receives the message (see below)

Conditions

Each condition row specifies a Field, an Operator, and an Expected Value. All rows must match for the trigger to fire. Supported operators: equals, not equals, contains, not contains, starts with, ends with, is set, is not set, greater than, less than.

Edge detection: the trigger compares the document after save to its state before save. A trigger on workflow_state = Academically Approved fires only when the state transitions to that value, not on every subsequent save.

Recipients

Each recipient row specifies where to deliver:

Recipient TypeResolves to
RoleAll enabled users holding that role
Person FieldA Link-to-Person field on the document or its parent
Specific PersonA hardcoded Person name

Communication Log

Every message creates a Communication Log row. Navigate to Communication > Communication Log to search, filter, and inspect the full ledger.

Key fields:

FieldDescription
DirectionOutbound (we sent) or Inbound (we received)
StatusQueued → Sending → Sent → Delivered → Read / Failed / Bounced / Cancelled
ChannelMedium used
Provider AccountWhich account handled delivery
PersonRecipient/sender linked to the spine
ReferenceThe triggering document (e.g., a Withdrawal Request)
Idempotency KeyUnique per logical message — guarantees no double-send

Status only moves forward. Delivery timestamps (sent_at, delivered_at, read_at) are stamped as each transition occurs.

Administrators can bypass the throttle queue for an urgent log using the Deliver Now button on the log form.

Portal Inbox

Students, instructors, and staff access their messages at Portal > Inbox. The inbox supports:

  • Received / Sent tab toggle
  • Filter by channel, category, or unread-only
  • Click a message to expand it; first expand marks it as read
  • Compose to start a new message:
    • Students can message their instructors (filtered to shared courses)
    • Instructors and staff can message students, with an optional course filter and an All Students shortcut
  • Reply to a received message — threaded back to the sender (within your messaging scope)

The Inbox item in the portal sidebar carries an unread count badge.

Desk Communication Inbox

Staff triage incoming communications from a dedicated desk page: Desk → Communication Inbox (/app/communication-inbox). It's a two-pane view — conversations on the left (one per Person, newest first; Unread or All), the full thread on the right (inbound and outbound, chat-style). Opening a conversation marks its inbound messages read; the reply box sends a threaded response on the same channel the person used (a Telegram question gets a Telegram reply), falling back to In-App. The desk navbar shows an unread badge (inbound messages awaiting triage) via the standard notification config. Replies and inbound messages are linked through the Communication Log's in_reply_to, and every message still lands on the Person's Conversation timeline.

Communication Preferences

Recipients manage their channel preferences at Portal > Preferences. From there they can:

  • Set their preferred language for communications
  • Opt in or out of Transactional and Promotional categories per channel (Emergency is always active)
  • Edit their mailing address (where printed letters go) — written straight to their Person, the single source of truth
  • View their registered channel addresses (email, mobile, Telegram chat id)
  • Connect their Telegram account via the signed deep-link button

The mailing address lives on the Person record (not duplicated onto Student/Alumni). Role records that need to show it — e.g. Alumni Profile — mirror it read-only via fetch_from, so there is one place to edit and no drift. Staff can still set it on the Person form (or, for students, on the Student record at intake, which seeds the Person).

Announcements

Seminary Announcements route through the Communication Log. An announcement picks a category (default Academic — set Emergency for a campus closure to bypass consent and the hourly throttle), one or more channels (default Email + In-App; add SMS / WhatsApp / Telegram for urgent reach), and an audience that can now include All Alumni. Each recipient × channel becomes one log; rich channels (Email, In-App) carry the full message, while length-limited channels (SMS / WhatsApp / Telegram) carry the Short Message (or the message stripped to plain text). Duplicate sends are deduplicated automatically by idempotency key.

Delivery and throttling

Outbound messages are queued (status = Queued) and drained by a background job that runs every five minutes. Each Channel Provider Account has an Hourly Limit; the drainer sends at most limit − messages_sent_in_the_last_hour messages per account per run.

Emergency messages bypass the limit and are delivered immediately.

In-App messages are also delivered immediately at queue time — there is no carrier to rate-limit, and the portal inbox should never wait for the cron drainer.

Failed deliveries are retried up to three times with exponential back-off (5 → 10 → 20 minutes). After the third failure the log status moves to Failed and a Frappe Error Log is written.

Publicado bajo la Licencia Pública General Affero GNU. | v1.4.0