Skip to content

Notification Templates

Keystone issues automated notifications alerting users to system changes and platform events. Administrators can customize these notifications using templates to reflect organization-specific branding and messaging.

Overriding Templates

Keystone renders HTML notifications using the Jinja2 templating engine. The location of custom templates is configurable via application settings.

When a notification is triggered, Keystone first checks for a custom template file. If no custom template is available, Keystone falls back to an internal default. The selected template is then rendered using the context data outlined below.

Email notification templates can be rendered and written to disk using the keystone-api render_templates command. It is strongly recommended to develop and test templates locally before deploying them to a production environment. See keystone-api render_templates --help for details.

Template Security

Template files are required to have no write permissions for users other than the owner or group (i.e., o+w). If a template file has world-writeable permissions, Keystone will refuse to load it. This restriction is provided for security and ensures templates cannot be modified by unauthorized users in production.

Notification templates are automatically sanitized to remove any JavaScript or externally loaded resources (e.g. CSS imports). Users familiar with the Jinja2 templating engine will also find certain Jinja features are not available, including access to application internals and the ability to bypass variable sanitation.

Partials

Partials are small, reusable fragments used to render content shared across templates. Overriding a partial changes the corresponding content for all notifications at once, making partials the preferred customization point for organization-specific greetings and branding. Each partial is rendered using the same context as the template including it.

Greeting

Template file: partials/greeting.html

The greeting partial renders the salutation displayed above the message body. The partial is included by the greeting block of the base template.

Available Template Fields
Field Name Type Description
recipient_name str Display name of the notified user.

Any field available to the including template can also be referenced from the partial.

Default Template Content
<p>Dear {{ recipient_name }},</p>

Signoff

Template file: partials/signoff.html

The signoff partial renders the closing remarks and signature displayed below the message body. The partial is included by the signoff block of the base template. The default content does not reference any context fields.

Default Template Content
<p style="padding-top: 10px;">
  Best regards,<br>The Keystone HPC Utility
</p>

Template file: partials/footer.html

The footer partial renders organization branding and copyright notices at the bottom of the message. The partial is included by the footer block of the base template. The default content does not reference any context fields.

Default Template Content
The Keystone HPC Utility <br> &copy; Better HPC

Templates

The following templates are available for customization.

Base Template

Template file: base.html

The base template serves as the parent layout for all notification content, providing top-level styling and structure. The template defines four content blocks that child templates override to inject content. The greeting, signoff, and footer blocks are rendered using partial templates by default.

Available Template Blocks
Block Name Description
greeting Salutation rendered above the message body.
main Main body content of the email notification.
signoff Closing remarks rendered below the message body.
footer Footer content displayed at the bottom of the message.
Default Template Content
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Keystone User Notification</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
      body {
          font-family: Helvetica, sans-serif;
          font-size: 16px;
          line-height: 1.4;
          background-color: #f5f5f5;
          margin: 0;
          padding: 0;
      }

      table.body {
          width: 100%;
      }

      table.main {
          width: 100%;
          background-color: #ffffff;
          border: 1px solid #eaeaea;
          border-radius: 0;
      }

      table.footer {
          width: 100%;
          text-align: center;
          padding-top: 24px;
          padding-bottom: 24px;
      }

      td.container {
          width: 800px;
          padding-top: 24px;
      }

      td.wrapper {
          padding: 24px;
      }

      td.footer-text {
          color: #a1a1a1;
          font-size: 11px;
      }
  </style>
</head>
<body>
<table role="presentation" class="body">
  <tr>
    <td>&nbsp;</td>
    <td class="container">
      <table role="presentation" class="main">
        <tr>
          <td class="wrapper">
            {% block greeting %}
              {% include "partials/greeting.html" %}
            {% endblock %}

            {% block main %}{% endblock %}

            {% block signoff %}
              {% include "partials/signoff.html" %}
            {% endblock %}
          </td>
        </tr>
      </table>
      <table role="presentation" class="footer">
        <tr>
          <td class="footer-text">
            {% block footer %}
              {% include "partials/footer.html" %}
            {% endblock %}
          </td>
        </tr>
      </table>
    </td>
    <td>&nbsp;</td>
  </tr>
</table>
</body>
</html>

General Notification

Template file: general.html

The general notification is used for one-off and administratively issued messages. The message body is provided by the notification sender and rendered as the entire content of the email.

Available Template Fields
Field Name Type Description
recipient_name str Display name of the notified user.
message str Body of the message sent to the user.
frontend_url str Base URL of the frontend application, without a trailing slash.
Default Template Content
{% extends "base.html" %}

{% block main %}
  <p>{{ message }}</p>
{% endblock %}

New Grant Record

Template file: grant_created.html

The grant created notification alerts team members that a new grant record has been added to their team. A summary of the new record is included in the message body.

Available Template Fields
Field Name Type Description
recipient_name str Display name of the notified user.
submitter_name str Display name of the user who created the grant record.
event_date datetime Date and time when the grant record was created.
grant_id int ID of the new grant record.
grant_title str Title of the grant.
grant_agency str Name of the agency funding the grant.
grant_number str or None Identification number assigned to the grant by the agency.
grant_pi str or None Name of the principal investigator.
grant_amount float Total amount awarded under the grant.
grant_start date Date when the grant period begins.
grant_end date or None Date when the grant period ends.
team_name str Name of the team associated with the grant.
team_slug str URL safe identifier for the team, suitable for building frontend URLs.
frontend_url str Base URL of the frontend application, without a trailing slash.
Default Template Content
{% extends "base.html" %}

{% block main %}
  {% set grant_url = frontend_url ~ '/team/' ~ team_slug ~ '/grant/' ~ grant_id %}

  <p>
    A new grant record was added to the <em>{{ team_name }}</em> team by
    <strong>{{ submitter_name }}</strong> on <strong>{{ event_date.strftime('%B %d, %Y') }}</strong>.
    The record

    {% if frontend_url %}
      <em><a href="{{ grant_url }}">{{ grant_title }}</a></em>
    {% else %}
      <em>{{ grant_title }}</em>
    {% endif %}

    (ID: {{ grant_id }}) is summarized below.
  </p>

  <table style="width: 100%; border-collapse: collapse; font-family: sans-serif; font-size: 14px;">
    <tbody>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Title</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ grant_title }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Funding Agency</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ grant_agency }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Grant Number</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ grant_number or '—' }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Principal Investigator</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ grant_pi or '—' }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Award Amount</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc;">${{ "{:,.2f}".format(grant_amount) }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Start Date</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ grant_start.strftime('%B %d, %Y') }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">End Date</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ grant_end.strftime('%B %d, %Y') if grant_end else '—' }}</td>
    </tr>
    </tbody>
  </table>

  <p style="padding-top: 10px;">
    You are receiving this notification because you have enabled grant notifications for the
    <em>{{ team_name }}</em> team.
    {% if frontend_url %}
      The record can be reviewed from the
      <a href="{{ grant_url }}">grant detail page</a>.
    {% endif %}
  </p>
{% endblock %}

Modified Grant Record

Template file: grant_modified.html

The grant modified notification alerts team members that an existing grant record has been updated. The message body reflects the state of the record after the modification was applied.

Available Template Fields
Field Name Type Description
recipient_name str Display name of the notified user.
submitter_name str Display name of the user who modified the grant record.
event_date datetime Date and time when the grant record was modified.
grant_id int ID of the modified grant record.
grant_title str Title of the grant.
grant_agency str Name of the agency funding the grant.
grant_number str or None Identification number assigned to the grant by the agency.
grant_pi str or None Name of the principal investigator.
grant_amount float Total amount awarded under the grant.
grant_start date Date when the grant period begins.
grant_end date or None Date when the grant period ends.
team_name str Name of the team associated with the grant.
team_slug str URL safe identifier for the team, suitable for building frontend URLs.
frontend_url str Base URL of the frontend application, without a trailing slash.
Default Template Content
{% extends "base.html" %}

{% block main %}
  {% set grant_url = frontend_url ~ '/team/' ~ team_slug ~ '/grant/' ~ grant_id %}

  <p>
    An existing grant record was modified for the <em>{{ team_name }}</em> team by
    <strong>{{ submitter_name }}</strong> on <strong>{{ event_date.strftime('%B %d, %Y') }}</strong>.
    The record

    {% if frontend_url %}
      <em><a href="{{ grant_url }}">{{ grant_title }}</a></em>
    {% else %}
      <em>{{ grant_title }}</em>
    {% endif %}

    (ID: {{ grant_id }}) was updated. The new record details are summarized below.
  </p>

  <table style="width: 100%; border-collapse: collapse; font-family: sans-serif; font-size: 14px;">
    <tbody>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Title</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ grant_title }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Funding Agency</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ grant_agency }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Grant Number</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ grant_number or '—' }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Principal Investigator</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ grant_pi or '—' }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Award Amount</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc;">${{ "{:,.2f}".format(grant_amount) }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Start Date</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ grant_start.strftime('%B %d, %Y') }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">End Date</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ grant_end.strftime('%B %d, %Y') if grant_end else '—' }}</td>
    </tr>
    </tbody>
  </table>

  <p style="padding-top: 10px;">
    You are receiving this notification because you have enabled grant notifications for the
    <em>{{ team_name }}</em> team.
    {% if frontend_url %}
      The record can be reviewed from the
      <a href="{{ grant_url }}">grant detail page</a>.
    {% endif %}
  </p>
{% endblock %}

Deleted Grant Record

Template file: grant_deleted.html

The grant deleted notification alerts team members that a grant record has been removed from their team. Only identifying details of the deleted record are available to the template.

Available Template Fields
Field Name Type Description
recipient_name str Display name of the notified user.
submitter_name str Display name of the user who deleted the grant record.
event_date datetime Date and time when the grant record was deleted.
grant_id int ID of the deleted grant record.
grant_title str Title of the deleted grant.
team_name str Name of the team associated with the grant.
team_slug str URL safe identifier for the team, suitable for building frontend URLs.
frontend_url str Base URL of the frontend application, without a trailing slash.
Default Template Content
{% extends "base.html" %}

{% block main %}
  <p>
    The grant record <em>{{ grant_title }}</em> (ID: {{ grant_id }}) was deleted from the
    <em>{{ team_name }}</em> team by <strong>{{ submitter_name }}</strong> on
    <strong>{{ event_date.strftime('%B %d, %Y') }}</strong>.
    The record no longer appears in the team's records and has been removed from any
    associated resource allocation requests.
  </p>

  <p style="padding-top: 10px;">
    You are receiving this notification because you have enabled grant notifications for the
    <em>{{ team_name }}</em> team.
  </p>
{% endblock %}

New Publication Record

Template file: publication_created.html

The publication created notification alerts team members that a new publication record has been added to their team. A summary of the new record is included in the message body.

Available Template Fields
Field Name Type Description
recipient_name str Display name of the notified user.
submitter_name str Display name of the user who created the publication record.
event_date datetime Date and time when the publication record was created.
publication_id int ID of the new publication record.
publication_title str Title of the publication.
publication_journal str or None Name of the journal the publication appears in.
publication_volume str or None Journal volume the publication appears in.
publication_issue str or None Journal issue the publication appears in.
publication_doi str or None Digital Object Identifier assigned to the publication.
publication_submitted date or None Date when the publication was submitted.
publication_published date or None Date when the publication was published.
team_name str Name of the team associated with the publication.
team_slug str URL safe identifier for the team, suitable for building frontend URLs.
frontend_url str Base URL of the frontend application, without a trailing slash.
Default Template Content
{% extends "base.html" %}

{% block main %}
  {% set publication_url = frontend_url ~ '/team/' ~ team_slug ~ '/publication/' ~ publication_id %}

  <p>
    A new publication record was added to the <em>{{ team_name }}</em> team by
    <strong>{{ submitter_name }}</strong> on <strong>{{ event_date.strftime('%B %d, %Y') }}</strong>.
    The record

    {% if frontend_url %}
      <em><a href="{{ publication_url }}">{{ publication_title }}</a></em>
    {% else %}
      <em>{{ publication_title }}</em>
    {% endif %}

    (ID: {{ publication_id }}) is summarized below.
  </p>

  <table style="width: 100%; border-collapse: collapse; font-family: sans-serif; font-size: 14px;">
    <tbody>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Title</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ publication_title }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Journal</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ publication_journal or '—' }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Volume</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ publication_volume or '—' }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Issue</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ publication_issue or '—' }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">DOI</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ publication_doi or '—' }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Date Submitted</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ publication_submitted.strftime('%B %d, %Y') if publication_submitted else '—' }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Date Published</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ publication_published.strftime('%B %d, %Y') if publication_published else '—' }}</td>
    </tr>
    </tbody>
  </table>

  <p style="padding-top: 10px;">
    You are receiving this notification because you have enabled publication notifications for the
    <em>{{ team_name }}</em> team.
    {% if frontend_url %}
      The record can be reviewed from the
      <a href="{{ publication_url }}">publication detail page</a>.
    {% endif %}
  </p>
{% endblock %}

Modified Publication Record

Template file: publication_modified.html

The publication modified notification alerts team members that an existing publication record has been updated. The message body reflects the state of the record after the modification was applied.

Available Template Fields
Field Name Type Description
recipient_name str Display name of the notified user.
submitter_name str Display name of the user who modified the publication record.
event_date datetime Date and time when the publication record was modified.
publication_id int ID of the modified publication record.
publication_title str Title of the publication.
publication_journal str or None Name of the journal the publication appears in.
publication_volume str or None Journal volume the publication appears in.
publication_issue str or None Journal issue the publication appears in.
publication_doi str or None Digital Object Identifier assigned to the publication.
publication_submitted date or None Date when the publication was submitted.
publication_published date or None Date when the publication was published.
team_name str Name of the team associated with the publication.
team_slug str URL safe identifier for the team, suitable for building frontend URLs.
frontend_url str Base URL of the frontend application, without a trailing slash.
Default Template Content
{% extends "base.html" %}

{% block main %}
  {% set publication_url = frontend_url ~ '/team/' ~ team_slug ~ '/publication/' ~ publication_id %}

  <p>
    An existing publication record was modified for the <em>{{ team_name }}</em> team by
    <strong>{{ submitter_name }}</strong> on <strong>{{ event_date.strftime('%B %d, %Y') }}</strong>.
    The record

    {% if frontend_url %}
      <em><a href="{{ publication_url }}">{{ publication_title }}</a></em>
    {% else %}
      <em>{{ publication_title }}</em>
    {% endif %}

    (ID: {{ publication_id }}) was updated. The new record details are summarized below.
  </p>

  <table style="width: 100%; border-collapse: collapse; font-family: sans-serif; font-size: 14px;">
    <tbody>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Title</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ publication_title }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Journal</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ publication_journal or '—' }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Volume</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ publication_volume or '—' }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Issue</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ publication_issue or '—' }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">DOI</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ publication_doi or '—' }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Date Submitted</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ publication_submitted.strftime('%B %d, %Y') if publication_submitted else '—' }}</td>
    </tr>
    <tr>
      <th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Date Published</th>
      <td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ publication_published.strftime('%B %d, %Y') if publication_published else '—' }}</td>
    </tr>
    </tbody>
  </table>

  <p style="padding-top: 10px;">
    You are receiving this notification because you have enabled publication notifications for the
    <em>{{ team_name }}</em> team.
    {% if frontend_url %}
      The record can be reviewed from the
      <a href="{{ publication_url }}">publication detail page</a>.
    {% endif %}
  </p>
{% endblock %}

Deleted Publication Record

Template file: publication_deleted.html

The publication deleted notification alerts team members that a publication record has been removed from their team. Only identifying details of the deleted record are available to the template.

Available Template Fields
Field Name Type Description
recipient_name str Display name of the notified user.
submitter_name str Display name of the user who deleted the publication record.
event_date datetime Date and time when the publication record was deleted.
publication_id int ID of the deleted publication record.
publication_title str Title of the deleted publication.
team_name str Name of the team associated with the publication.
team_slug str URL safe identifier for the team, suitable for building frontend URLs.
frontend_url str Base URL of the frontend application, without a trailing slash.
Default Template Content
{% extends "base.html" %}

{% block main %}
  <p>
    The publication record <em>{{ publication_title }}</em> (ID: {{ publication_id }}) was deleted
    from the <em>{{ team_name }}</em> team by <strong>{{ submitter_name }}</strong> on
    <strong>{{ event_date.strftime('%B %d, %Y') }}</strong>.
    The record no longer appears in the team's records and has been removed from any
    associated resource allocation requests.
  </p>

  <p style="padding-top: 10px;">
    You are receiving this notification because you have enabled publication notifications for the
    <em>{{ team_name }}</em> team.
  </p>
{% endblock %}

New Allocation Request

Template file: request_created.html

The request created notification alerts users that a new resource allocation request has been submitted on behalf of their team. The message body includes a snapshot of the request as it was submitted, including the service units requested on each cluster.

Available Template Fields
Field Name Type Description
recipient_name str Display name of the notified user.
submitter_name str Display name of the user who submitted the request.
event_date date or None Date when the allocation request was submitted.
request_id int ID of the allocation request being notified about.
request_title str Title of the allocation request.
request_status str Human readable status of the allocation request.
request_active date or None Date when the allocation request becomes active.
request_expire date or None Date when the allocation request expires.
team_name str Name of the team associated with the allocation request.
team_slug str URL safe identifier for the team, suitable for building frontend URLs.
allocations tuple[dict] Allocated resources tied to the request. Each item includes:
├ cluster str Name of the cluster where the resource is allocated.
└ requested int Number of service units requested (or 0 if unavailable).
frontend_url str Base URL of the frontend application, without a trailing slash.
Default Template Content
{% extends "base.html" %}

{% block main %}
  {% set request_url = frontend_url ~ '/team/' ~ team_slug ~ '/request/' ~ request_id %}

  <p>
    A new HPC resource request has been submitted by <strong>{{ submitter_name }}</strong> for the
    <em>{{ team_name }}</em> team. The request

    {% if frontend_url %}
      <em><a href="{{ request_url }}">{{ request_title }}</a></em>
    {% else %}
      <em>{{ request_title }}</em>
    {% endif %}

    (ID: {{ request_id }}) was submitted on <strong>{{ event_date.strftime('%B %d, %Y') }}</strong>
    and is now pending administrator review.
  </p>

  <p>
    The table below summarizes the requested resources. Please note these values are pending review and
    the total awarded resources may differ from those shown below.
  </p>

  <table style="width: 100%; border-collapse: collapse; table-layout: fixed; font-family: sans-serif; font-size: 14px;">
    <thead>
    <tr style="background-color: #f2f2f2;">
      <th style="width: 70%; text-align: center; padding: 8px; border: 1px solid #ccc;">Cluster</th>
      <th style="width: 30%; text-align: center; padding: 8px; border: 1px solid #ccc;">Requested SUs</th>
    </tr>
    </thead>
    <tbody>
    {% for allocation in allocations %}
      <tr>
        <td style="text-align: center; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">
          {{ allocation.cluster }}
        </td>
        <td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
          {{ "{:,}".format(allocation.requested) }}
        </td>
      </tr>
    {% endfor %}
    </tbody>
  </table>

  <p style="padding-top: 10px">
    <strong>What Does This Mean</strong>
  </p>

  <p>
    Resource requests are used by teams to petition for new or additional HPC resources.
    Formal approval is required by an HPC administrator before your team can access the requested resources.
    Any existing resources that have already been awarded to your team are not affected by this request.
  </p>

  <p style="padding-top: 10px;">
    <strong>Recommended Actions</strong>
  </p>

  <p>
    No action is required from you at this time.
    An administrator will review the request and decide which resources to award and in what quantity.
    {% if frontend_url %}
      In the meantime, you can monitor any status updates online from the
      <a href="{{ request_url }}">request detail page</a>.
    {% endif %}
  </p>
{% endblock %}

Reviewer Assigned

Template file: request_reviewer_assigned.html

The reviewer assigned notification alerts a staff user that they have been assigned as a reviewer for a resource allocation request. In addition to a snapshot of the request, the template has access to any other reviewers assigned to the same request so reviewers can coordinate their work.

Available Template Fields
Field Name Type Description
recipient_name str Display name of the notified user.
submitter_name str Display name of the user who made the reviewer assignment.
event_date datetime Date and time when the assignment was made.
request_id int ID of the allocation request being notified about.
request_title str Title of the allocation request.
request_status str Human readable status of the allocation request.
request_submitted date or None Date when the allocation request was submitted.
request_active date or None Date when the allocation request becomes active.
request_expire date or None Date when the allocation request expires.
request_coassignees tuple[dict] Other reviewers assigned to the request. Each item includes:
└ name str Display name of the coassigned reviewer.
team_name str Name of the team associated with the allocation request.
team_slug str URL safe identifier for the team, suitable for building frontend URLs.
allocations tuple[dict] Allocated resources tied to the request. Each item includes:
├ cluster str Name of the cluster where the resource is allocated.
└ requested int Number of service units requested (or 0 if unavailable).
frontend_url str Base URL of the frontend application, without a trailing slash.
Default Template Content
{% extends "base.html" %}

{% block main %}
  {% set request_url = frontend_url ~ '/team/' ~ team_slug ~ '/request/' ~ request_id %}

  <p>
    You have been assigned by <strong>{{ submitter_name }}</strong> as a reviewer for the
    <em>{{ team_name }}</em> team's allocation request

    {% if frontend_url %}
      <em><a href="{{ request_url }}">{{ request_title }}</a></em>
    {% else %}
      <em>{{ request_title }}</em>
    {% endif %}

    (ID: {{ request_id }}).
    The HPC resources included in the request are listed below.
  </p>

  <table style="width: 100%; border-collapse: collapse; table-layout: fixed; font-family: sans-serif; font-size: 14px;">
    <thead>
    <tr style="background-color: #f2f2f2;">
      <th style="width: 70%; text-align: center; padding: 8px; border: 1px solid #ccc;">Cluster</th>
      <th style="width: 30%; text-align: center; padding: 8px; border: 1px solid #ccc;">Requested SUs</th>
    </tr>
    </thead>
    <tbody>
    {% for allocation in allocations %}
      <tr>
        <td style="text-align: center; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">
          {{ allocation.cluster }}
        </td>
        <td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
          {{ "{:,}".format(allocation.requested) }}
        </td>
      </tr>
    {% endfor %}
    </tbody>
  </table>

  <p style="padding-top: 10px;">
    <strong>Recommended Actions</strong>
  </p>

  <p>
    A review is required to determine which HPC resources should be awarded and in what amount(s).
    If additional information is required, further clarification can be requested from the submitter using
    by submitting a comment on the request.
    {% if frontend_url %}
      You can review the request online from the <a href="{{ request_url }}">request detail page</a>.
    {% endif %}
  </p>
{% endblock %}

New Request Comment

Template file: request_comment_created.html

The comment created notification alerts users that a new comment has been posted on a resource allocation request. The comment body is rendered as written and the template indicates whether the comment was marked private to staff reviewers.

Available Template Fields
Field Name Type Description
recipient_name str Display name of the notified user.
submitter_name str Display name of the comment author.
event_date datetime Date and time when the comment was created.
request_id int ID of the allocation request being notified about.
request_title str Title of the allocation request.
request_status str Human readable status of the allocation request.
comment_content str Body of the comment, sanitized for safe rendering as HTML.
comment_is_private bool Whether the comment is only visible to staff reviewers.
team_name str Name of the team associated with the allocation request.
team_slug str URL safe identifier for the team, suitable for building frontend URLs.
frontend_url str Base URL of the frontend application, without a trailing slash.
Default Template Content
{% extends "base.html" %}

{% block main %}
  {% set request_url = frontend_url ~ '/team/' ~ team_slug ~ '/request/' ~ request_id %}

  <p>
    A new comment was made by <strong>{{ submitter_name }}</strong> on allocation request

    {% if frontend_url %}
      <em><a href="{{ request_url }}">{{ request_title }}</a></em>
    {% else %}
      <em>{{ request_title }}</em>
    {% endif %}

    (ID: {{ request_id }}).
    {% if comment_is_private %}
      This comment was marked as private and is only visible to staff users.
    {% endif %}
  </p>

  <table style="width: 100%; border-collapse: collapse; font-family: sans-serif; font-size: 14px; margin-top: 20px">
    <tbody>
    <tr>
      <td style="text-align: left; padding: 8px 12px; border: 1px solid #ccc; border-bottom: none; background-color: #f2f2f2;">
        <strong>{{ submitter_name }}</strong> commented on {{ event_date.strftime('%B %d, %Y') }}
      </td>
    </tr>
    <tr>
      <td style="text-align: left; padding: 12px; border: 1px solid #ccc; background-color: #f9f9f9; overflow-wrap: break-word; word-break: break-word;">{{ comment_content }}</td>
    </tr>
    </tbody>
  </table>

  <p style="padding-top: 10px;">
    <strong>Recommended Actions</strong>
  </p>

  <p>
    A comment does not necessarily change the status of a request.
    Unless otherwise indicated any service units already awarded to your team remain unaffected.
    {% if frontend_url %}
      If you wish to reply to the comment or to view the allocation request, you can do so from the
      <a href="{{ request_url }}">request detail page</a>.
    {% endif %}
  </p>
{% endblock %}

Request Status Change

Template file: request_status_changed.html

The status changed notification alerts users that the status of a resource allocation request has changed. The default template branches on the request_status field to render dedicated messaging for approved requests, including a summary of the awarded resources, and falls back to a generic message describing the old and new status for all other transitions.

Available Template Fields
Field Name Type Description
recipient_name str Display name of the notified user.
submitter_name str Display name of the user who changed the request status.
event_date datetime Date and time when the status change was recorded.
request_id int ID of the allocation request being notified about.
request_title str Title of the allocation request.
request_status str Human readable status of the request after the change.
request_status_previous str Human readable status of the request before the change.
request_active date or None Date when the allocation request becomes active.
request_expire date or None Date when the allocation request expires.
team_name str Name of the team associated with the allocation request.
team_slug str URL safe identifier for the team, suitable for building frontend URLs.
allocations tuple[dict] Allocated resources tied to the request. Each item includes:
├ cluster str Name of the cluster where the resource is allocated.
├ requested int Number of service units requested (or 0 if unavailable).
└ awarded int or None Number of service units awarded (or None if not yet awarded).
frontend_url str Base URL of the frontend application, without a trailing slash.
Default Template Content
{% extends "base.html" %}

{% block main %}
  {% set request_url = frontend_url ~ '/team/' ~ team_slug ~ '/request/' ~ request_id %}

  {% macro request_link() %}
    {% if frontend_url %}
      <em><a href="{{ request_url }}">{{ request_title }}</a></em>
    {% else %}
      <em>{{ request_title }}</em>
    {% endif %}
  {% endmacro %}

  {% if request_status == 'Approved' %}
    <p>
      The <em>{{ team_name }}</em> team's allocation request
      {{ request_link() }} (ID: {{ request_id }}) was approved on
      <strong>{{ event_date.strftime('%B %d, %Y') }}</strong>.
    </p>

    <p>
      The HPC resources listed below have been approved for use starting <strong>
      {{ request_active.strftime('%B %d, %Y')  }}</strong>.
      These resource
      {% if request_expire %}
        will expire on <strong>{{ request_expire.strftime('%B %d, %Y') }}</strong>.
      {% else %}
        do not have an expiration date.
      {% endif %}
    </p>

    <table
        style="width: 100%; border-collapse: collapse; table-layout: fixed; font-family: sans-serif; font-size: 14px;">
      <thead>
      <tr style="background-color: #f2f2f2;">
        <th style="width: 40%; text-align: center; padding: 8px; border: 1px solid #ccc;">Cluster</th>
        <th style="width: 30%; text-align: center; padding: 8px; border: 1px solid #ccc;">Requested SUs</th>
        <th style="width: 30%; text-align: center; padding: 8px; border: 1px solid #ccc;">Awarded SUs</th>
      </tr>
      </thead>
      <tbody>
      {% for allocation in allocations %}
        <tr>
          <td style="text-align: center; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">
            {{ allocation.cluster }}
          </td>
          <td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
            {{ "{:,}".format(allocation.requested) }}
          </td>
          <td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
            {{ "{:,}".format(allocation.awarded) if allocation.awarded is not none else '—' }}
          </td>
        </tr>
      {% endfor %}
      </tbody>
    </table>

    <p style="padding-top: 10px;">
      <strong>What Does This Mean</strong>
    </p>

    <p>
      An administrator has reviewed the allocation request and awarded the resources listed above.
      These resources will be allocated alongside any others your team already holds.
      Unused service units do not carry over once an allocation expires.
    </p>

  {% else %}
    <p>
      The <em>{{ team_name }}</em> team's allocation request
      {{ request_link() }} (ID: {{ request_id }}) was changed from
      <strong>{{ request_status_previous }}</strong> to <strong>{{ request_status }}</strong> on
      <strong>{{ event_date.strftime('%B %d, %Y') }}</strong>.
    </p>
  {% endif %}

  <p style="padding-top: 10px;">
    <strong>Recommended Actions</strong>
  </p>

  <p>
    If you have questions regarding this change, please contact your HPC administrator.
    Please note it may take several hours for any changes mentioned above to go into full effect.
    {% if frontend_url %}
      You can review this request online from the <a href="{{ request_url }}">request detail page</a>.
    {% endif %}
  </p>
{% endblock %}

Upcoming Resource Expiration

Template file: request_nearing_expiration.html

The upcoming expiration notification alerts users that one or more of their active resource allocations is nearing its expiration date.

Available Template Fields
Field Name Type Description
recipient_name str Display name of the notified user.
request_id int ID of the allocation request being notified about.
request_title str Title of the allocation request.
request_active date Date when the allocation request became active.
request_expire date Date when the allocation request expires.
request_days_until_expire int Number of days remaining until the request expires.
team_name str Name of the team associated with the allocation request.
team_slug str URL safe identifier for the team, suitable for building frontend URLs.
allocations tuple[dict] Allocated resources tied to the request. Each item includes:
├ awarded int Number of service units awarded (or 0 if unavailable).
└ cluster str Name of the cluster where the resource is allocated.
upcoming_requests tuple[dict] Other active or pending requests for the team. Each item includes:
├ active date or None Date when the upcoming request became active.
├ expire date or None Date when the upcoming request expires.
├ id int ID of the upcoming allocation request.
├ status str Human readable status of the upcoming allocation request.
├ submitted date or None Date when the upcoming request was submitted.
└ title str Title of the upcoming allocation request.
frontend_url str Base URL of the frontend application, without a trailing slash.
Default Template Content
{% extends "base.html" %}

{% block main %}
  {% set request_url = frontend_url ~ '/team/' ~ team_slug ~ '/request/' ~ request_id %}

  <p>
    This is a reminder that the <em>{{ team_name }}</em> team's HPC allocation

    {% if frontend_url %}
      <em><a href="{{ request_url }}">{{ request_title }}</a></em>
    {% else %}
      <em>{{ request_title }}</em>
    {% endif %}

    (ID: {{ request_id }}) is expiring in
    <strong>{{ request_days_until_expire }} day{{ "s" if request_days_until_expire != 1 }}</strong>,
    on <strong>{{ request_expire.strftime('%B %d, %Y') }}</strong>. After this
    date, any unused service units granted by this allocation will no longer
    be accessible to your team.
  </p>

  <p>
    This expiration only applies to service units awarded under the allocation
    mentioned above. Any service units awarded under different allocations held
    by your team remain unaffected. The table below summarizes the service units
    associated with the expiring allocation:
  </p>

  <table style="width: 100%; border-collapse: collapse; font-family: sans-serif; font-size: 14px;">
    <thead>
    <tr style="background-color: #f2f2f2;">
      <th style="text-align: center; padding: 8px; border: 1px solid #ccc;">Cluster</th>
      <th style="text-align: center; padding: 8px; border: 1px solid #ccc;">Awarded SUs</th>
    </tr>
    </thead>
    <tbody>
    {% for allocation in allocations %}
      <tr>
        <td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
          {{ allocation.cluster }}
        </td>
        <td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
          {{ "{:,}".format(allocation.awarded) }}
        </td>
      </tr>
    {% endfor %}
    </tbody>
  </table>

  <p style="padding-top: 10px;">
    <strong>Recommended Actions</strong>
  </p>

  {% if upcoming_requests %}
    <p>
      The following table includes all active or upcoming allocations currently
      associated with the <em>{{ team_name }}</em> team. Please take a moment to
      review each allocation and confirm your team has sufficient resources moving
      forward. If your team anticipates needing resources beyond those listed
      below, please have a team administrator submit a new allocation request
      at their earliest convenience.
    </p>

    <table
        style="width: 100%; border-collapse: collapse; table-layout: fixed; font-family: sans-serif; font-size: 14px;">
      <thead>
      <tr style="background-color: #f2f2f2;">
        <th style="width: 8%; text-align: center; padding: 8px; border: 1px solid #ccc;">ID</th>
        <th style="width: 30%; text-align: left; padding: 8px; border: 1px solid #ccc;">Title</th>
        <th style="width: 12%; text-align: center; padding: 8px; border: 1px solid #ccc;">Status</th>
        <th style="width: 16%; text-align: center; padding: 8px; border: 1px solid #ccc;">Submitted</th>
        <th style="width: 16%; text-align: center; padding: 8px; border: 1px solid #ccc;">Begins</th>
        <th style="width: 16%; text-align: center; padding: 8px; border: 1px solid #ccc;">Expires</th>
      </tr>
      </thead>
      <tbody>
      {% for upcoming in upcoming_requests %}
        <tr>
          <td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
            {{ upcoming.id }}
          </td>
          <td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">
            {% if frontend_url %}
              <a href="{{ frontend_url }}/team/{{ team_slug }}/request/{{ upcoming.id }}">{{ upcoming.title }}</a>
            {% else %}
              {{ upcoming.title }}
            {% endif %}
          </td>
          <td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
            {{ upcoming.status }}
          </td>
          <td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
            {{ upcoming.submitted.strftime('%B %d, %Y') if upcoming.submitted else '—' }}
          </td>
          <td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
            {{ upcoming.active.strftime('%B %d, %Y') if upcoming.active else '—' }}
          </td>
          <td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
            {{ upcoming.expire.strftime('%B %d, %Y') if upcoming.expire else '—' }}
          </td>
        </tr>
      {% endfor %}
      </tbody>
    </table>

  {% else %}
    <p>
      The <em>{{ team_name }}</em> team has no upcoming or active allocations beyond the one described above.
      Once this allocation expires on <strong>{{ request_expire.strftime('%B %d, %Y') }}</strong>, your team will have
      zero remaining service units. If your team requires additional service units, please have a team administrator
      submit a new allocation request at their earliest convenience.
    </p>

    <p>
      If your team does not require additional service units, then no action is required at this time.
    </p>
  {% endif %}
{% endblock %}

Expired Resource Allocation

Template file: request_past_expiration.html

The past expiration notification alerts users that one or more of their active resource allocations has expired and that the resources granted under that allocation are no longer available for use.

Available Template Fields
Field Name Type Description
recipient_name str Display name of the notified user.
request_id int ID of the allocation request being notified about.
request_title str Title of the allocation request.
request_active date Date when the allocation request became active.
request_expire date Date when the allocation request expired.
team_name str Name of the team associated with the allocation request.
team_slug str URL safe identifier for the team, suitable for building frontend URLs.
allocations tuple[dict] Allocated resources tied to the request. Each item includes:
├ awarded int Number of service units awarded (or 0 if unavailable).
├ cluster str Name of the cluster where the resource is allocated.
└ final int Number of service units used by the team (or 0 if unavailable).
upcoming_requests tuple[dict] Other active or pending requests for the team. Each item includes:
├ active date or None Date when the upcoming request became active.
├ expire date or None Date when the upcoming request expires.
├ id int ID of the upcoming allocation request.
├ status str Human readable status of the upcoming allocation request.
├ submitted date or None Date when the upcoming request was submitted.
└ title str Title of the upcoming allocation request.
frontend_url str Base URL of the frontend application, without a trailing slash.
Default Template Content
{% extends "base.html" %}

{% macro percent(numerator, denominator) %}
  {% if denominator %}
    ({{ '%.0f' % ((numerator / denominator) * 100) }}%)
  {% else %}
    &nbsp;
  {% endif %}
{% endmacro %}

{% block main %}
  {% set request_url = frontend_url ~ '/team/' ~ team_slug ~ '/request/' ~ request_id %}

  <p>
    This is a reminder that the <em>{{ team_name }}</em> team's HPC allocation

    {% if frontend_url %}
      <em><a href="{{ request_url }}">{{ request_title }}</a></em>
    {% else %}
      <em>{{ request_title }}</em>
    {% endif %}

    (ID: {{ request_id }}) expired on
    <strong>{{ request_expire.strftime('%B %d, %Y') }}</strong>. Moving forward,
    any unused service units granted by this allocation are no longer usable
    by your team.
  </p>

  <p>
    The table below summarizes your team's total resource usage under this allocation:
  </p>

  <table style="width: 100%; border-collapse: collapse; font-family: sans-serif; font-size: 14px;">
    <thead>
    <tr style="background-color: #f2f2f2;">
      <th style="text-align: center; padding: 8px; border: 1px solid #ccc;">Cluster</th>
      <th style="text-align: center; padding: 8px; border: 1px solid #ccc;">Awarded SUs</th>
      <th style="text-align: center; padding: 8px; border: 1px solid #ccc;">Utilized SUs</th>
    </tr>
    </thead>
    <tbody>
    {% for allocation in allocations %}
      <tr>
        <td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
          {{ allocation.cluster }}
        </td>
        <td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
          {{ "{:,}".format(allocation.awarded) }}
        </td>
        <td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
          {{ "{:,}".format(allocation.final) }} {{ percent(allocation.final, allocation.awarded) }}
        </td>
      </tr>
    {% endfor %}
    </tbody>
  </table>

  <p style="padding-top: 10px">
    <strong>What Does This Mean</strong>
  </p>

  <p>
    On {{ request_active.strftime('%B %d, %Y') }} your team was granted the
    service unit allocation listed above. This allocation expired on
    {{ request_expire.strftime('%B %d, %Y') }} and any unused service units
    are no longer available for use.
  </p>

  <p>
    Any resources granted to your team by other allocation requests remain
    unaffected. The system will automatically apply as much of your system
    usage as possible to the expiring request, maximizing the total service
    units available in any remaining allocations.
  </p>

  <p style="padding-top: 10px;">
    <strong>Recommended Actions</strong>
  </p>

  {% if upcoming_requests %}
    <p>
      The following table includes all active or upcoming allocations currently
      associated with the <em>{{ team_name }}</em> team. Please take a moment to
      review each allocation and confirm your team has sufficient resources moving
      forward. If your team anticipates needing resources beyond those listed
      below, please have a team administrator submit a new allocation request
      at their earliest convenience.
    </p>

    <table
        style="width: 100%; border-collapse: collapse; table-layout: fixed; font-family: sans-serif; font-size: 14px;">
      <thead>
      <tr style="background-color: #f2f2f2;">
        <th style="width: 8%; text-align: center; padding: 8px; border: 1px solid #ccc;">ID</th>
        <th style="width: 30%; text-align: left; padding: 8px; border: 1px solid #ccc;">Title</th>
        <th style="width: 12%; text-align: center; padding: 8px; border: 1px solid #ccc;">Status</th>
        <th style="width: 16%; text-align: center; padding: 8px; border: 1px solid #ccc;">Submitted</th>
        <th style="width: 16%; text-align: center; padding: 8px; border: 1px solid #ccc;">Begins</th>
        <th style="width: 16%; text-align: center; padding: 8px; border: 1px solid #ccc;">Expires</th>
      </tr>
      </thead>
      <tbody>
      {% for upcoming in upcoming_requests %}
        <tr>
          <td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
            {{ upcoming.id }}
          </td>
          <td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">
            {% if frontend_url %}
              <a href="{{ frontend_url }}/team/{{ team_slug }}/request/{{ upcoming.id }}">{{ upcoming.title }}</a>
            {% else %}
              {{ upcoming.title }}
            {% endif %}
          </td>
          <td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
            {{ upcoming.status }}
          </td>
          <td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
            {{ upcoming.submitted.strftime('%B %d, %Y') if upcoming.submitted else '—' }}
          </td>
          <td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
            {{ upcoming.active.strftime('%B %d, %Y') if upcoming.active else '—' }}
          </td>
          <td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
            {{ upcoming.expire.strftime('%B %d, %Y') if upcoming.expire else '—' }}
          </td>
        </tr>
      {% endfor %}
      </tbody>
    </table>

  {% else %}
    <p>
      The <em>{{ team_name }}</em> team has no upcoming or active allocations.
      If your team requires additional service units, please have a team administrator submit a new allocation request
      at their earliest convenience.
      If your team does not require additional service units, then no action is required at this time.
    </p>
  {% endif %}
{% endblock %}