#5270·hrms

Replace per-doctype notification with FW Notification

Author: iamkhanraheelCreated Sep 15, 2026Updated Sep 15, 2026

Problem

Seven doctypes each independently reimplement the same "check a Settings toggle → render an Email Template → send" pipeline, spread across two different Settings doctypes:

Doctype Settings field(s) Trigger
Leave Application send_leave_notification, leave_approval_notification_template, leave_status_notification_template status change
Interview send_interview_confirmation, interview_confirmation_template button click
Job Offer hiring_sender_email (template picked ad hoc per send) button click
Expense Claim (#4895, not yet merged) send_expense_notification, expense_claim_approval_notification_template, expense_claim_status_notification_template status change
Exit Interview exit_questionnaire_notification_template button click (bulk)
Salary Slip Payroll Settings.email_template on submit, or bulk button
Employee Reminders (birthday / anniversary / holiday) send_birthday_reminders etc., hardcoded message, no Email Template scheduled (cron)

Each one is a custom variant of the same idea: a toggle, a template, and hand-written Python to render and send. That's duplicated logic to maintain, duplicated bugs to fix, and no consistent way for a site to customize any of it.

Proposed direction

Frappe core already has a general-purpose mechanism for this: the Notification doctype. It covers every trigger shape these doctypes need via Notification.send(doc):

  1. Automatic: a persisted Notification record with a built-in event (Save / Submit / Cancel / Value Change / Days Before, etc.). No code required.
  2. Manual, fixed template: a persisted Notification with event="Method". A whitelisted handler does a permission check, then calls doc.run_method("<name>"). Document.run_method() always calls run_notifications(method), which matches Notification records the same way lifecycle hooks do.
  3. Manual, ad-hoc per-send choice: only needed where a user picks the template at send time (Job Offer). Build an unsaved Notification(...) from the picks and call .send(doc) directly; it doesn't require persistence.

No flexibility is lost: only Job Offer ever had a per-send picker to begin with; everything else always sent the same fixed content.

Per-doctype mapping

Shape Doctypes What gets written
Automatic Leave Application, Expense Claim, Salary Slip (on-submit path) Notification record(s), zero code
Manual, fixed template Interview, Exit Interview, Salary Slip (bulk send) Notification (event=Method) + permission check + run_method
Manual, ad-hoc Job Offer Unsaved Notification(...), .send(doc)

What stays custom, and why

  • Employee Reminders (birthday/anniversary/holiday): repeats every year by matching month and day (ignoring year), and broadcasts to other employees as recipients, neither of which Notification models. Worth a smaller independent fix: swap the hardcoded Python string for a real Email Template, keep the matching/recipient logic as-is.

Scope / rollout

  • Leave Application: pure automatic, needs three Notification records (pending approval, status update, cancelled). (#5187)
  • Salary Slip: automatic on-submit path + manual bulk-send path.
  • Exit Interview: manual, fixed template; recipient already resolved to a plain email field via set_employee_email(), straightforward.
  • Expense Claim: automatic, same shape as Leave Application.
  • Interview: manual, fixed template; recipient list comes from a child table (Interview Detail.interviewer), the hardest of the "automatic/manual" group.
  • Job Offer: ad-hoc/ephemeral shape, needs the in-memory Notification technique.
  • Employee Reminders: out of scope for the Notification migration; swap hardcoded message for an Email Template only.