Skip to content

saved-search-match

Source of truth: packages/notifications/src/translations.ts

DomainChannelsRecipients
alertsin-app + email + pushSaved-search owner
SourceContext
apps/worker/src/alerts.ts:332processSavedSearchScan
interface SavedSearchMatchData {
searchName: string;
matchCount: number;
searchUrl: string;
}

The caller invokes triggerWorkflow('saved-search-match', recipientIds, data) where data is the SavedSearchMatchData shape above. The server pre-translates the payload via getTranslatedPayload('saved-search-match', recipientLocale, data) from @repo/notifications before sending, producing { inAppBody, emailSubject, emailBody } for the dashboard templates to render.

Knock dashboard template body: {{ inAppBody }}.

LocalePreview source
es🏠 ${p.matchCount} ${plural(p.matchCount, 'nueva propiedad coincide', 'nuevas propiedades coinciden')} con "${p.searchName}"
en🏠 ${p.matchCount} new ${plural(p.matchCount, 'listing matches', 'listings match')} "${p.searchName}"

Subject: {{ emailSubject }}. Body: {{ emailBody }} (HTML mode, dashboard escaping OFF — every user-supplied string is escaped server-side via escapeHtml from @repo/notifications).

LocaleSubject preview
es${p.matchCount} ${plural(p.matchCount, 'nueva propiedad coincide', 'nuevas propiedades coinciden')} con "${p.searchName}"
en${p.matchCount} new ${plural(p.matchCount, 'listing matches', 'listings match')} "${p.searchName}"

Email body is composed by emailBody.es in packages/notifications/src/translations.ts via the layout components in packages/notifications/src/email-layout.ts (wrapEmailLayout, ctaButton, calloutBox, quoteBlock, propertyCard, detailTable / detailRow). Body source is not inlined here because it spans tens of lines per locale — open the source file for the full template.

Push delivery is configured at the Knock dashboard level for this workflow (FCM channel step with {{ inAppBody }} as the notification body). iOS APNs is deferred until the dashboard p8 key is wired — see Configuration → mobile push.