Skip to main content

Overview

Saved Replies are pre-written response templates that agents can use to quickly respond to common ticket scenarios. They support template variables and can be scoped globally, to specific teams, or as personal templates.

Saved Reply DocType Fields

The HD Saved Reply DocType contains the following fields:
title
string
required
Unique title for the saved reply
message
HTML
required
Template content with support for Jinja2 variables
scope
Select
default:"Global"
Visibility scope: Global, Team, or Personal
teams
Table
List of teams that can access this reply (when scope is Team)

Template Variables

Saved reply messages support Jinja2 templating with access to:

Ticket Fields

All HD Ticket fields are available:
  • {{ subject }} - Ticket subject
  • {{ description }} - Ticket description
  • {{ status }} - Ticket status
  • {{ priority }} - Ticket priority
  • {{ raised_by }} - Customer email
  • And all other ticket fields

User Fields

All User fields for the current agent:
  • {{ full_name }} - Agent’s full name
  • {{ email }} - Agent’s email
  • {{ phone }} - Agent’s phone number
  • And all other user fields

Get Rendered Saved Reply

Render a saved reply template with ticket and user context.
This endpoint is defined in helpdesk/api/saved_replies.py:7 and is restricted to agents only
@frappe.whitelist()
@agent_only
def get_rendered_saved_reply(ticket_id: str | int, saved_reply_id: str | None = None):
    """Render a saved reply template
    
    Combines saved reply template with ticket and user data.
    
    Args:
        ticket_id: ID of the ticket context
        saved_reply_id: ID of the saved reply to render
    
    Returns:
        Rendered HTML string with variables replaced
    """
ticket_id
string
required
The ticket ID to use as context for template rendering
saved_reply_id
string
required
The saved reply ID to render

Response

Returns the rendered HTML string with all template variables replaced with actual values from the ticket and current user.

Template Context

The template is rendered with two data sources merged:
  1. Ticket data: All fields from the HD Ticket document
  2. User data: All fields from the current user’s User document

Error Handling

Throws an error if saved_reply_id is not provided:
  • Message: “Please provide saved_reply_id”

Example Request

import frappe

# Render a saved reply for a specific ticket
rendered = frappe.call(
    "helpdesk.api.saved_replies.get_rendered_saved_reply",
    ticket_id="12345",
    saved_reply_id="Password Reset Instructions"
)

print(rendered)
# Output: "<p>Dear customer, we've received your request...</p>"

Example Template

Saved Reply message:
<p>Dear {{ raised_by }},</p>

<p>Thank you for contacting us regarding: <strong>{{ subject }}</strong></p>

<p>I'm {{ full_name }}, and I'll be assisting you with this {{ priority }} priority ticket.</p>

<p>Best regards,<br>
{{ full_name }}<br>
{{ email }}</p>
Rendered output:
<p>Dear [email protected],</p>

<p>Thank you for contacting us regarding: <strong>Cannot login to my account</strong></p>

<p>I'm John Smith, and I'll be assisting you with this High priority ticket.</p>

<p>Best regards,<br>
John Smith<br>
[email protected]</p>

Create Saved Reply

Create a new saved reply template:
import frappe

# Create a global saved reply
saved_reply = frappe.get_doc({
    "doctype": "HD Saved Reply",
    "title": "Welcome Message",
    "scope": "Global",
    "message": """<p>Hi {{ raised_by }},</p>
    <p>Thank you for contacting support. We've received your ticket 
    regarding {{ subject }} and will respond within 24 hours.</p>
    <p>Best regards,<br>{{ full_name }}</p>"""
})
saved_reply.insert()

print(f"Created: {saved_reply.name}")

Create Team-Scoped Reply

import frappe

# Create a team-specific saved reply
saved_reply = frappe.get_doc({
    "doctype": "HD Saved Reply",
    "title": "Technical Support Greeting",
    "scope": "Team",
    "message": "<p>Technical support response template...</p>"
})

# Add teams
saved_reply.append("teams", {
    "team": "Technical Support"
})
saved_reply.append("teams", {
    "team": "Engineering"
})

saved_reply.insert()

Create Personal Reply

import frappe

# Create a personal saved reply (only visible to creator)
saved_reply = frappe.get_doc({
    "doctype": "HD Saved Reply",
    "title": "My Personal Template",
    "scope": "Personal",
    "message": "<p>Personal template content...</p>"
})
saved_reply.insert()

List Saved Replies

Query saved replies with scope filtering:
import frappe

# Get all global saved replies
global_replies = frappe.get_list(
    "HD Saved Reply",
    filters={"scope": "Global"},
    fields=["name", "title", "message"],
    order_by="title asc"
)

for reply in global_replies:
    print(f"{reply.title}")

Get Replies for Current User’s Team

import frappe

# Get current agent's team
agent = frappe.get_doc("HD Agent", frappe.session.user)

# Get team-specific replies
team_replies = frappe.get_list(
    "HD Saved Reply Team",
    filters={"team": agent.agent_group},
    fields=["parent"],
    pluck="parent"
)

for reply_id in team_replies:
    reply = frappe.get_doc("HD Saved Reply", reply_id)
    print(f"{reply.title}")

Update Saved Reply

import frappe

# Update saved reply content
reply = frappe.get_doc("HD Saved Reply", "Welcome Message")
reply.message = "<p>Updated template content...</p>"
reply.save()

Delete Saved Reply

import frappe

frappe.delete_doc("HD Saved Reply", "Old Template")

Scope Behavior

Global Scope

  • Visible to all agents
  • Can be used on any ticket
  • Typically used for common responses

Team Scope

  • Visible only to agents in specified teams
  • Multiple teams can be selected
  • Useful for department-specific responses

Personal Scope

  • Only visible to the creator
  • Private templates for individual agent use

Settings Control

The HD Settings DocType has a field that controls saved reply scope:
import frappe

# Disable global scope for saved replies
frappe.db.set_value(
    "HD Settings",
    "HD Settings",
    "disable_saved_replies_global_scope",
    1
)
When disable_saved_replies_global_scope is enabled, only team and personal saved replies are available.

Advanced Template Example

<p>Hi {{ raised_by.split('@')[0].title() }},</p>

<p>I hope this email finds you well.</p>

{% if priority == "High" %}
<p><strong>We've marked this as a high priority ticket and will prioritize our response.</strong></p>
{% endif %}

<p>Regarding your ticket: <em>{{ subject }}</em></p>

<p>{{ description[:100] }}...</p>

<p>We'll investigate this issue and get back to you shortly.</p>

<p>Ticket Details:</p>
<ul>
  <li>Ticket ID: {{ name }}</li>
  <li>Status: {{ status }}</li>
  <li>Created: {{ creation.strftime('%B %d, %Y at %I:%M %p') }}</li>
</ul>

<p>Best regards,<br>
{{ full_name }}<br>
{{ email }}</p>

Permissions

Saved Reply permissions:
  • Agent Role: Create, read, update, delete
  • Agent Manager Role: Create, read, update, delete
  • System Manager: Full access

Standard Frappe API Methods

  • frappe.get_doc("HD Saved Reply", reply_id) - Get single reply
  • frappe.get_list("HD Saved Reply", filters={...}) - Query replies
  • frappe.delete_doc("HD Saved Reply", reply_id) - Delete reply

Build docs developers (and LLMs) love