Skip to main content

Alert

Source: src/data/alert/

Service

createAdminNotificationService / AdminNotificationService

Factory: createAdminNotificationService() — returns AdminNotificationService.

Persists tenant-scoped admin notifications for order and payment lifecycle events.

MethodParametersReturnsDescription
notify(ctx: RequestContext, input: { type: AdminNotificationEventType, message: string, entityType: string, entityId: string })Promise<void>Persists a tenant-scoped admin notification event for operational follow-up

AdminNotificationEventType is one of ORDER_SUBMITTED, PAYMENT_CONFIRMED, PAYMENT_EXCEPTION.

The service maps notification types to alert types:

  • ORDER_SUBMITTEDNEW_ORDER
  • PAYMENT_CONFIRMEDNEW_ORDER
  • PAYMENT_EXCEPTIONPAYMENT_FAILED

Repository

AlertRepository

MethodParametersReturns
getById(ctx: RequestContext, id: string)Promise<AlertRecord | null>
listByTenant(ctx: RequestContext)Promise<AlertRecord[]>
create(ctx: RequestContext, record: AlertRecord)Promise<void>
update(ctx: RequestContext, id: string, patch: Partial<Omit<AlertRecord, 'id' | 'tenantId'>>)Promise<AlertRecord>
delete(ctx: RequestContext, id: string)Promise<void>

AlertRecord

FieldType
idstring
tenantIdstring
createdAtDate
updatedAtDate
dataRecord<string, unknown> (optional)

Validators

alertTypeSchema

Enum: LOW_INVENTORY, NEW_ORDER, PAYMENT_FAILED, FULFILLMENT_FAILED

alertSeveritySchema

Enum: INFO, WARN, ERROR

alertSchema

FieldTypeRequiredDefault
tenantIdstringYes
typealertTypeSchemaYes
severityalertSeveritySchemaYes
messagestringYes
entityTypestringNo
entityIdstringNo
isReadbooleanNofalse
createdAtDate (coerced)Yes
updatedAtDate (coerced)Yes

Inferred Types

  • Alertz.infer<typeof alertSchema>
  • AlertTypez.infer<typeof alertTypeSchema>
  • AlertSeverityz.infer<typeof alertSeveritySchema>

NotificationService

Source: src/data/alert/notification.service.ts

Factory: createNotificationService({ cache }) — returns NotificationService.

Sends Discord messages to admin channels and customer DMs. Deduplicates per eventId + consumerId using Valkey (24 h TTL). Retries once on Discord 429 responses (honouring Retry-After).

MethodParametersReturnsDescription
notifyAdminChannel(ctx, tenantId, payload, opts)Promise<NotifyResult>Posts to tenant's announcement/admin channel
notifyCustomerDm(ctx, discordUserId, payload, opts)Promise<NotifyResult>DMs a Discord user (create DM channel → send)

NotifyResult{ skipped: boolean; messageId?: string }

NotificationPayload{ content?: string; embeds?: DiscordEmbed[]; components?: DiscordActionRow[] }

NotifyOptions{ eventId: string; consumerId: string; botToken: string; channelId?: string }


Notification Consumers

Source: src/data/alert/notification.consumers.ts

Registers event handlers on the app event bus. Called from src/registries/events.registry.ts.

EventConsumer IDTarget
order.createdorder.created:adminChannelAdmin channel — approval embed + buttons
order.createdorder.created:customerDmCustomer DM — order received + payment instructions
payment.rejectedpayment.rejected:adminChannelAdmin channel — rejection audit post
payment.confirmedpayment.confirmed:customerDmCustomer DM — itemized receipt embed (items, subtotal, discount, total, payment method, ETA)
order.shippedorder.shipped:customerDmCustomer DM — tracking or pickup notice
order.refundedorder.refunded:customerDmCustomer DM — refund notice + reason

DM preferences: each customer DM is gated on the matching customer.dmPreferences.<key> flag. If the flag is false, the DM is skipped and an audit entry is written.

Email notifications: after each customer DM, if emailPort is wired, customer.email is on file, and the corresponding customer.emailPreferences.<key> flag is true, a transactional email is sent via EmailPort. Email failures are non-blocking — they are swallowed with .catch(() => undefined) so a send failure never prevents the DM from being delivered. Email templates:

EventTemplate file
order.createdsrc/data/alert/email-templates/order-received.template.ts
payment.confirmedsrc/data/alert/email-templates/payment-confirmed.template.ts
order.shippedsrc/data/alert/email-templates/order-shipped.template.ts
order.refundedsrc/data/alert/email-templates/order-refunded.template.ts

DM opt-out: if customer.dmOptOut === true, customer DMs are skipped and an audit log entry (notification.dm_skipped.opt_out) is written instead.

inventory.low is intentionally NOT handled here — that alert is owned by the Inventory feature and fires a separate @everyone mention.


EmailPort

Source: src/types/email.types.ts, src/ports/email.port.ts, src/adapters/sendgrid-email.adapter.ts

Port contract for transactional email delivery. Two implementations:

ImplementationFactoryCondition
SendGrid (HTTPS v3 API)createSendGridEmailAdapter({ apiKey, from })EMAIL_PROVIDER=sendgrid + SENDGRID_API_KEY + EMAIL_FROM set
No-opcreateNoopEmailPort()Default when SendGrid is not configured

EmailPort interface:

MethodParametersReturns
sendEmailSendInputPromise<EmailSendResult>

EmailSendInput:

FieldTypeRequired
tostringYes
subjectstringYes
htmlstringYes
textstringYes
tagsstring[]No

EmailSendResult: { accepted: boolean; messageId?: string }

The SendGrid adapter uses the native Node.js https module (no SDK dependency). It throws on 4xx responses (permanent delivery failure) and on 5xx responses (transient; caller may retry via outbox). The from address is sourced from the EMAIL_FROM environment variable.