> ## Documentation Index
> Fetch the complete documentation index at: https://docs.soundchecklive.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Multiday SendGrid Email Variables

> The additive day-aware template variables the API sends to each SendGrid dynamic template, with paste-ready Handlebars for editing the templates by hand

Soundcheck emails are **SendGrid dynamic templates** — the HTML lives in SendGrid,
not in the repo. **api-events** supplies the `dynamicData` map. This page is the
contract for the **day-aware variables** added for the multiday per-day staffing
feature so a template editor knows exactly what keys arrive and how to render them.

All variables here are **additive and backward compatible**: single-day
engagements send exactly what they always did. `eventDate` is upgraded to a
formatted range string only for multiday engagements; `performanceFee` always
stays the offer-level fee. A template that ignores the new keys keeps working.

> [!NOTE]
>
> The API sets a key **only when it has a value**. Always guard with `{{#if …}}`
> / `{{#each …}}`. Never assume a key is present.

***

## Event title vs offer / request title

`eventName` is the **offer or availability-request title** (often the role, e.g.
`Front-of-House Engineer`). `eventTitle` is the **linked gig title** (e.g.
`Saturday Night Headliner`). The API sends `eventTitle` only when it is present
and different from `eventName`, so templates can show both without duplicating
the same line.

```handlebars theme={null}
<div>{{eventName}}</div>
{{#if eventTitle}}
<div>{{eventTitle}}</div>
{{/if}}
```

Subject lines can append the gig the same way:

```handlebars theme={null}
{{eventName}}{{#if eventTitle}} — {{eventTitle}}{{/if}}
```

Invitation emails already use the gig title as `eventName` and do not send a
separate `eventTitle`.

***

## The shared day array

Every offer/availability recipient email (and the offer acceptance email) carries
these keys when the engagement spans more than one day:

| Variable | Type | When present | Notes |
| - | - | - | - |
| `isMultiDay` | bool | offers & availability requests with day rows | `false` for single-day |
| `dayCount` | int | same | number of scheduled days |
| `days` | array | same | one object per day (see below) |
| `rateCardTotal` | string | **per-day-rated offers only** | formatted Σ of effective day rates, e.g. `$750.00` |
| `eventDate` | string | always | single day: `Friday, August 14, 2026`; multiday: `Friday, August 14 – Sunday, August 16, 2026` |

Each object in `days`:

| Field | Example | Notes |
| - | - | - |
| `date` | `2026-08-14` | machine date (`YYYY-MM-DD`) |
| `dateFormatted` | `Fri, Aug 14` | short display date |
| `time` | `2:00 PM – 11:00 PM` | empty string when the day has no call times |
| `fee` | `$350.00` | empty string unless the offer is **per-day-rated** |

An offer is **per-day-rated** when any of its days sets its own `fee` override;
then every day's `fee` shows its effective rate (`day.fee ?? offer.fee`) and
`rateCardTotal` is the sum. Availability requests never carry a fee, so their day
objects always have an empty `fee` and no `rateCardTotal`.

### Paste-ready day list

```handlebars theme={null}
{{#if isMultiDay}}
  <p>{{dayCount}} days</p>
  <ul>
    {{#each days}}
      <li>
        {{dateFormatted}}{{#if time}} · {{time}}{{/if}}{{#if fee}} · {{fee}}{{/if}}
      </li>
    {{/each}}
  </ul>
  {{#if rateCardTotal}}<p><strong>Total: {{rateCardTotal}}</strong></p>{{/if}}
{{else}}
  <p>{{eventDate}}{{#if eventTime}} · {{eventTime}}{{/if}}</p>
{{/if}}
```

***

## Offer acceptance confirmation — accepted days

`SENDGRID_TEMPLATE_FOR_CREW_OFFER_ACCEPTANCE` additionally receives the
recipient's **accepted** day subset, plus an `.ics` file attachment (`gig.ics`)
covering those days:

| Variable | Type | When present | Notes |
| - | - | - | - |
| `acceptedDays` | array | ≥1 accepted day | same object shape as `days` |
| `acceptedDayCount` | int | same | |
| `acceptedTotal` | string | per-day-rated offers only | formatted Σ of accepted effective rates |

### Paste-ready accepted-days block

```handlebars theme={null}
{{#if acceptedDays}}
  <p>You're confirmed for {{acceptedDayCount}} day{{#if acceptedTotal}} ({{acceptedTotal}}){{/if}}:</p>
  <ul>
    {{#each acceptedDays}}
      <li>{{dateFormatted}}{{#if time}} · {{time}}{{/if}}{{#if fee}} · {{fee}}{{/if}}</li>
    {{/each}}
  </ul>
{{/if}}
```

***

## Requester response emails — structured summary

The response-to-requester templates now receive the day summary as its **own**
variables instead of it being glued into `responseNotes`. `responseNotes` is once
again the responder's free-text notes only.

| Variable | Type | Offer / invitation template | Availability template |
| - | - | - | - |
| `responseNotes` | string | responder's free-text notes ONLY | same |
| `responseSummary` | string | `Accepted Fri, Jul 10 · Declined Sun, Jul 12` | `Available … · Unavailable …` |
| `acceptedDays` | string\[] | ✅ (`["Fri, Jul 10"]`) | — |
| `declinedDays` | string\[] | ✅ | — |
| `availableDays` | string\[] | — | ✅ |
| `unavailableDays` | string\[] | — | ✅ |

Affected templates: `SENDGRID_TEMPLATE_FOR_CREW_OFFER_RESPONSE_TO_REQUESTER`,
`SENDGRID_TEMPLATE_FOR_AVAILABILITY_RESPONSE_TO_REQUESTER`,
`SENDGRID_TEMPLATE_FOR_INVITATION_RESPONSE_TO_REQUESTER`.

### Requester card layout

Keep the card short: rollup badge + per-day datetime rows with action
status + optional notes. Prefer `days[].status` over `responseSummary`
(summary is still sent for SMS / legacy).

Each `days[]` item may include:

| Field | Example |
| - | - |
| `dateFormatted` | `Thu, Jul 16` |
| `time` | `1:00 PM – 3:10 PM` |
| `status` | `Accepted` / `Declined` / `Available` / `Unavailable` |
| `statusKey` | `ACCEPTED` / `DECLINED` / `AVAILABLE` / `UNAVAILABLE` |

Badge (API never sends `PARTIAL`; derive it in the template):

* Offer: both `acceptedDays` + `declinedDays` → **Partial** (amber);
  only accepted → **Accepted**; only declined → **Declined**
* Availability: both `availableDays` + `unavailableDays` → **Partial**;
  otherwise **Available** / **Unavailable**

```handlebars theme={null}
{{#each days}}
  <div>
    <strong>{{dateFormatted}}</strong>{{#if time}} · {{time}}{{/if}}
    {{#if status}} · {{status}}{{/if}}
  </div>
{{/each}}

{{#if responseNotes}}<blockquote>{{responseNotes}}</blockquote>{{/if}}
```

***

## Invitation emails

Invitation templates key their date variables off the **event**, not off offer
day rows. They now also receive the span:

| Variable | Type | When present | Notes |
| - | - | - | - |
| `eventDate` | string | always | range form for multiday events (same format as above) |
| `eventEndDate` | string | event has an end date | `Sunday, August 16, 2026` |
| `isMultiDay` | bool | event has an event date | `false` for single-day |
| `dayCount` | int | same | inclusive day span |

`daysUntilEvent` and `responseDeadline` remain keyed to the event **start**.

Affected templates: `SENDGRID_TEMPLATE_FOR_ORG_INVITATION`,
`SENDGRID_TEMPLATE_FOR_NON_ORG_INVITATION`,
`SENDGRID_TEMPLATE_FOR_INVITATION_UPDATE`,
`SENDGRID_TEMPLATE_FOR_INVITATION_REMINDER`,
`SENDGRID_TEMPLATE_FOR_INVITATION_ACCEPTANCE`. The acceptance email attaches a
multiday `.ics` (one VEVENT per event day) for multiday events.

```handlebars theme={null}
<p>
  {{eventDate}}
  {{#if isMultiDay}}<span class="badge">{{dayCount}}-day event</span>{{/if}}
</p>
```

***

## Which template gets which variables

| Template env var | day array (`isMultiDay`/`dayCount`/`days`/`rateCardTotal`) | accepted-day vars | requester summary vars | invitation span vars |
| - | :-: | :-: | :-: | :-: |
| `SENDGRID_TEMPLATE_FOR_CREW_OFFER` | ✅ | | | |
| `SENDGRID_TEMPLATE_FOR_CREW_OFFER_REMINDER` | ✅ | | | |
| `SENDGRID_TEMPLATE_FOR_CREW_OFFER_UPDATE` | ✅ | | | |
| `SENDGRID_TEMPLATE_FOR_CREW_OFFER_CANCELLED` | ✅ | | | |
| `SENDGRID_TEMPLATE_FOR_CREW_OFFER_ACCEPTANCE` | ✅ | ✅ (+ `gig.ics`) | | |
| `SENDGRID_TEMPLATE_FOR_CREW_OFFER_RESPONSE_TO_REQUESTER` | | | ✅ (accepted/declined) | |
| `SENDGRID_TEMPLATE_FOR_AVAILABILITY_REQUEST` | ✅ (no fees) | | | |
| `SENDGRID_TEMPLATE_FOR_AVAILABILITY_REQUEST_REMINDER` | ✅ (no fees) | | | |
| `SENDGRID_TEMPLATE_FOR_AVAILABILITY_REQUEST_UPDATE` | ✅ (no fees) | | | |
| `SENDGRID_TEMPLATE_FOR_AVAILABILITY_REQUEST_CANCELLED` | ✅ (no fees) | | | |
| `SENDGRID_TEMPLATE_FOR_AVAILABILITY_RESPONSE_CONFIRMATION` | ✅ (no fees) | | | |
| `SENDGRID_TEMPLATE_FOR_AVAILABILITY_RESPONSE_TO_REQUESTER` | | | ✅ (available/unavailable) | |
| `SENDGRID_TEMPLATE_FOR_ORG_INVITATION` / `…_NON_ORG_INVITATION` | | | | ✅ |
| `SENDGRID_TEMPLATE_FOR_INVITATION_UPDATE` / `…_REMINDER` | | | | ✅ |
| `SENDGRID_TEMPLATE_FOR_INVITATION_ACCEPTANCE` | | | | ✅ (+ multiday `.ics`) |
| `SENDGRID_TEMPLATE_FOR_INVITATION_RESPONSE_TO_REQUESTER` | | | ✅ (accepted/declined) | |

***

## Where this is built (for engineers)

* Shared day-var builder: `api-events/src/systems/crew-offers/engagementEmailDays.ts`
  (`DayEmailVars`, `AcceptedDayVars`, date-range + money formatting).
* Offer/availability renderers that emit the vars:
  `api-events/src/systems/crew-offers/crewOfferNotificationRenderer.ts`,
  `api-events/src/systems/availability-requests/availabilityRequestResponseRenderer.ts`.
* Requester response vars + attachment plumbing:
  `api-events/src/systems/crew-offers/crewOfferResponseNotificationProcessor.ts`.
* Invitation span vars: `api-events/src/systems/invitations/invitationNotificationRenderer.ts`.
* ICS generation (multiday event + offer accepted-days segments):
  `api-events/src/systems/crew-offers/crewOfferAcceptedIcs.ts`, `api-events/src/utils/ics.ts`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.