Skip to main content
All Docs
FeaturesPurple PepperUpdated April 8, 2026

Tenant Notifications

Tenant Notifications

The tenant notification system automatically sends agency-branded emails and SMS messages to tenants at key points in the tenancy lifecycle. It is powered by Inngest and supports opt-out tracking, idempotent delivery, and a full audit trail.

How It Works

Notifications are dispatched by sending an Inngest event from anywhere in the application. The tenantNotificationSend function picks up the event, resolves tenant and agency details, checks opt-out preferences, and delivers via email (Resend) and/or SMS (Twilio).

Every delivery — whether successful, skipped, or opted-out — is recorded in the tenant_notification_log table.

Triggering a Notification

Send the tenant/notification.send Inngest event with the following payload:

await inngest.send({
  name: "tenant/notification.send",
  data: {
    orgId: "org_123",
    portalTokenId: "token_456",
    notificationType: "welcome", // see Notification Types below
    // Optional context fields:
    tenancyTermId: "term_789",
    taskTitle: "Pay holding deposit",
    documentType: "Gas Safety Certificate",
    daysRemaining: 30,
    endDate: "15 March 2025",
  },
});

Required Fields

FieldTypeDescription
orgIdstringThe organisation ID
portalTokenIdstringThe tenant portal token used to look up tenant details and opt-out preferences
notificationTypestringThe type of notification to send (see below)

Optional Context Fields

FieldTypeUsed By
tenancyTermIdstringAll types; links the notification to a specific tenancy term
taskTitlestringtask_assigned — name of the task assigned to the tenant
documentTypestringcompliance_update — e.g. "Gas Safety Certificate", "EPC", "EICR"
daysRemainingnumberrenewal_notice — days until tenancy end
endDatestringrenewal_notice — human-readable end date, e.g. "15 March 2025"

Notification Types

welcome

Sent when a tenancy is created. Includes:

  • Portal access link
  • Tenancy start date
  • Monthly rent amount

Delivery is idempotent — a welcome notification is only sent once per portal token.

task_assigned

Sent when a task is assigned to the tenant. Includes:

  • Task title (from taskTitle)
  • Cost breakdown (holding deposit, tenancy agreement)

compliance_update

Sent when a compliance document is updated. Includes:

  • Document type (from documentType), e.g. Gas Safety Certificate, EPC, EICR

renewal_notice

Sent when a renewal notice is dispatched. Includes:

  • Days remaining (from daysRemaining)
  • Tenancy end date (from endDate)
  • Urgency indicators based on proximity to end date

Delivery is idempotent — renewal notices are deduplicated per portal token.

Agency Branding

All email templates use the agencyName field from your agency branding configuration. Emails are delivered white-labelled under the agency's name rather than a platform identity.

Opt-Out Tracking

Opt-outs are stored in the tenant_notification_log table as records with channel = "opt_out". When an opt-out record exists for a given portal token:

  • Both email and SMS delivery are skipped
  • No further notifications of any type are sent to that tenant

To opt a tenant out, insert a record into tenant_notification_log with channel = "opt_out" for the relevant portalTokenId.

Delivery Channels

ChannelProviderBehaviour when unavailable
EmailResend (via existing sendEmail wrapper)Error is retried per Inngest retry policy
SMSTwilioGracefully skipped if the tenant has no phone number on record

Audit Logging

Every notification dispatched (regardless of channel or outcome) is written to the audit log via logAudit. This provides a full history of tenant communications at the organisation level.

Database Table: tenant_notification_log

The tenant_notification_log table records every notification event.

ColumnTypeDescription
idtext (PK)Auto-generated UUID
orgIdtextOrganisation ID
portalTokenIdtextTenant portal token
notificationTypetextOne of welcome, task_assigned, compliance_update, renewal_notice
channeltextOne of email, sms, opt_out
statustextDelivery status
messageIdtextProvider message ID (Resend or Twilio)
metadatajsonbAdditional context
createdAttimestampRecord creation timestamp

Indexes: orgId, portalTokenId, notificationType, channel, createdAt

Infrastructure Notes

  • The Inngest function applies concurrency limits and throttling to prevent burst sending.
  • The function uses NonRetriableError for permanent failures (e.g. missing tenant record) to avoid infinite retries.
  • Batch tracking is supported for bulk notification scenarios.
  • The schema file src/db/tenant-notification-schema.ts is included in drizzle.config.ts so migrations are generated automatically.