Skip to content

Shared Package ​

@activation-sys/shared — Types, Zod schemas, constants, and error classes used across all apps.

Installation ​

Already included in the monorepo workspace.

ts
import { createUserSchema, AppError, SIM_LIMITS } from '@activation-sys/shared';

Exports ​

ts
// TypeScript types
export * from './types/index';

// Zod validation schemas
export * from './schemas/index';

// Constants
export * from './constants/index';

// Error classes
export { AppError, AuthError, ValidationError, NotFoundError, ActivationError, PaymentError, OperatorError, SupportError, toApiResponse };

// Notification errors
export { NotificationError, notificationError, NOTIFICATION_ERROR_CODES, NOTIFICATION_ERROR_MESSAGES }
export type { NotificationErrorCode }

Types ​

Status and role types are string unions (not TypeScript enums):

ts
type UserRole = 'end_user' | 'b2b_admin' | 'activation_officer' | 'super_admin' | 'support' | 'admin';
type ManageableArea =
  | 'activation-records' | 'packages' | 'officer' | 'support' | 'b2b' | 'notifications'
  | 'customers' | 'store' | 'identity-detail' | 'audit'
  | 'staff-users' | 'company-people' | 'company-onboarding';
type ActivationStatus = 'pending' | 'in_progress' | 'activated' | 'failed' | 'cancelled';
type OperatorSlug = 'stc' | 'mobily' | 'zain';
type IdentityType = 'citizen' | 'resident' | 'visitor';
type PaymentMethod = 'mada' | 'stc_pay' | 'apple_pay' | 'google_pay' | 'visa' | 'mastercard' | 'stripe';
type SupportedCurrency = 'USD' | 'SAR' | 'AED' | 'EUR' | 'GBP' | 'EGP';

types/areas.ts also holds the area rules. VIEW_ONLY_AREAS (activation-records, customers, identity-detail, audit) are granted at View only, and the admin user schemas reject them in managedAreasWrite. AREA_REQUIRES maps a dependent area to its parent (identity-detail to customers, company-people and company-onboarding to b2b), and STAFF_ROLE_REQUIREMENTS lists what a grantor must hold for the fixed roles (activation_officer: officer Manage and customers View; support: support Manage and customers View). effectiveLevels(managedAreas, managedAreasWrite) turns an admin's stored pair into per-area View or Manage levels. canGrant(actor, target) says whether an actor may hand out a role and areas (only levels it holds itself, never staff-users Manage, anything for the owner), and coversUser(actor, target) says whether the actor is not weaker than an existing user, so may edit or remove them (never the owner). The ADMIN_USER_SCOPE_EXCEEDED error code (403) covers a change outside the actor's own access.

This shared union retains legacy values for existing consumers. Runtime API methods are mada, visa, mastercard, apple_pay and stc_pay only; gateway selection is Mock/Moyasar/Paymob (PAYMENT_GATEWAY_IDS = moyasar, paymob). See payment contract.

Support and Officer Types ​

Type or constantContract
TicketFull ticket row, including type/source, links, due/response/decision timestamps and refund decision fields
CustomerTicket, CustomerTicketDetailCustomer-safe ticket fields and non-internal comments; staff author ids are hidden
StaffTicketListItem, StaffTicketDetailStaff ticket fields, masked customer, assignee, overdue/awaiting-response markers, comments, audit history and linked-record state
TicketCountsopen, inProgress, overdue, awaitingResponse, awaitingDecision
OfficerQueueActivationItemFallout row with claim, age, company flag and live refund request id
OfficerActivationEventOne status change of an activation: id, fromStatus, toStatus, source, note, createdAt
OfficerQueueActivationDetailQueue or completion-tool row plus heldState, completion claim, package, serial suffix, payment, operator capabilities and events
OfficerQueueStoreItemRead-only store reconciliation row
StoreOrderStatusEventOne issuance change of a store order: id, fromState, toState, errorCode (the code after the change), source (StoreTransitionSource), note, createdAt
OfficerQueueCountsfailed, stuck, claimedByMe, and store attention counts
OfficerCompletionActivationOne paid, not-active activation as the completion tool lists it: package, operator, SIM, payment, stale, answerLost, claim and refund request state
OfficerCompletionCustomerA matched customer with their masked phone, identity type and up to 50 OfficerCompletionActivation rows
OfficerCompletionSearchResponsematchedOn (phone or serial) and the matched customers
OfficerHeldStateretryable, needs_investigation, in_flight, none
OfficerOperatorLookupWhich operator lookup answered: request (by our reference) or line (by SIM serial)
OfficerOperatorCheckOutcomeWhat a direct operator check did: activated, retry_allowed, not_active, check_again
OfficerOperatorCheckResponseactivationId, lookup, the operator's raw state, and the outcome
OfficerResolveResponseactivationId and the resulting status, after resolve records a confirmed outcome
OperatorCapabilitiesrepeatSafeActivation?, requestLookup?, lineLookup?, each absent or false meaning the operator does not offer it
OperatorLookupStateactive, not_found, in_progress — an operator lookup's raw answer
TICKET_TYPEScustomer_request, refund_request
TICKET_SOURCEScustomer, staff
TICKET_STATUSESopen, in_progress, resolved, closed
TICKET_MARKERSoverdue, awaiting_response
REFUND_REASONSservice_not_delivered, activation_failed, duplicate_charge, customer_request, other
REFUND_DECISIONSapproved, rejected

TICKET_DUE_WINDOW_DAYS is 5, TICKET_REASON_MAX_LENGTH is 500, and TICKET_REFERENCE_PREFIX is TKT-.

REFUND_DECIDER_ROLE (constants/support.ts) names the one role that decides refund requests, currently super_admin; isRefundDecider(role) tests membership. The decision route admits only this role, and the same role is refused before any read on both refund-request-opening routes, so a request is never left without a possible decider. customerTicketPath(ticketId), supportTicketPath(ticketId), and officerTicketPath(ticketId) build the in-app link paths the ticket notifications carry; STAFF_TICKET_PATH_PREFIXES (/support/tickets/, /officer/tickets/) lists the staff paths, which carry no locale segment, for a portal to recognise and follow as its own pages. PAID_ORDER_STATUSES (paid, activating, completed) and isPaidOrderStatus(status) define the order statuses that count as paid for support work: a refund request can be opened only for one of them, and the customer record's orderPaid flag reads the same set.

Notification Types ​

types/notification.ts exports the notification vocabulary and contracts through the package root.

TypeShape or Values
NotificationLabelReadonly en and ar strings.
NotificationLocaleen, ar.
NotificationChannelin_app, email, sms, push.
NotificationChannelEntryReadonly bilingual label and enabled flag.
NotificationQueuedChannelemail, sms.
OptOutEligibleChannelemail, sms, push.
NotificationCategoryactivation, payment, esim_delivery, company, admin, system, announcement.
NotificationCategoryEntryReadonly bilingual label and transactional flag.
NotificationSeverityinfo, warning, critical. Presentation only.
NotificationUrgencynormal, urgent. Selects queue priority only.
NotificationEventKeyactivation.completed, activation.failed, payment.succeeded, company.approved, contract.ready, admin.invited, esim.issued, ticket.opened, ticket.assigned, ticket.replied, ticket.resolved, ticket.refund_requested.
NotificationTemplateKeyThe same event keys plus custom.
NotificationDeliveryOutcomequeued, sending, sent, delivered, failed_terminal, suppressed.
NotificationRetryBookkeepingattempts, nullable errorCode, nullable errorText and nullable failedAt. Separate from the outcome.
NotificationSuppressionReasonaccount_inactive, channel_opted_out, no_address, provider_unsupported.
NotificationBatchStatusscheduled, running, finished, cancelled.
NotificationAudienceKindusers, company, role, all.
NotificationAudienceusers carries readonly userIds. company carries companyId. role carries a UserRole. all carries no selector.
EventNotificationTriggerInputkind: 'event', eventKey and the common trigger fields below.
CustomNotificationTriggerInputkind: 'custom', category and the common trigger fields below.
NotificationTriggerInputUnion of the event and custom trigger inputs.
NotificationTriggerResultrecipientsTotal, notificationsCreated, notificationsDeduped, deliveriesCreated, deliveriesQueued, deliveriesSuppressed and fullyEnqueued.
NotificationEmailJobdeliveryId, notificationId and channel: 'email'. No address or content.
NotificationSmsJobdeliveryId, notificationId and channel: 'sms'. No address or content.
NotificationChannelJobUnion of the email and SMS jobs.
NotificationOrchestrationJobrun_batch with batchId, reconcile_handoffs with staleAfterMs and batchSize, or normalise_callback with eventId. Each branch uses the kind discriminator.
RenderedNotificationRequired body. Optional subject, html and attachments. Each attachment has filename, Buffer content and cid.
NotificationRenderFnAsync function receiving templateKey, params, channel, locale and optional { userId } context. Returns RenderedNotification.
NotificationSendResultaccepted, rejected_retryable, rejected_terminal, unsupported.
NotificationSendOutcomeRequired result. Optional provider, providerMessageId, errorCode and errorText.
NotificationProviderCallbackEventexternalEventId, eventType, nullable providerMessageId, normalisedStatus, nullable reasonCode and nullable providerTimestamp. Status is sent, delivered, failed_terminal or ignored.
NotificationSendMetadeliveryId, notificationId, queued channel and locale.
NotificationSendFnAsync function receiving the loaded address, rendered content and NotificationSendMeta. Returns NotificationSendOutcome.

Both trigger branches require audience, params and dedupeKey. Optional fields are severity, urgency, channels, expiresAt, batchId and createdBy. Event params carry validated identifiers and template inputs. Custom params carry bilingual copy. Addresses and credentials are not trigger params. A supplied channel list narrows the registered channels. The dedupe key identifies one occurrence per recipient.

Notification jobs do not extend BaseJob. They have no payload type, jobId or createdAt fields. JobType and QueueJob in types/queue.ts cover activation, payment and store only. Use the notification job types directly for the three notification lanes.

A chunk transaction that does not commit makes the trigger throw after logging ids only, so a caller never treats a partial write as a complete occurrence. The dedupe key retries the same occurrence later. fullyEnqueued can be false after storage succeeds. The flag does not prove delivery. Disabled push creates no delivery row. In-app delivery is written directly as delivered. Only announcement is non-transactional and consults channel opt-outs.

Allowed outcome changes are explicit. Repeated or backward changes are rejected by canAdvanceDeliveryOutcome.

Current OutcomeAllowed Next Outcomes
queuedsending, sent, delivered, failed_terminal, suppressed
sendingsent, delivered, failed_terminal, suppressed
sentdelivered, failed_terminal
deliveredNone
failed_terminalNone
suppressedNone

Zod Schemas ​

All schemas use Zod v4 and can be used for both validation and type inference:

ts
import { createUserSchema, activationSchema, paymentIntentSchema } from '@activation-sys/shared';

// Validate request body
const result = createUserSchema.safeParse(body);
if (!result.success) { /* handle validation error */ }
// result.data is typed as CreateUser

Available schemas: createUserSchema, updateUserSchema, operatorSchema, createOperatorSchema, packageSchema, createPackageSchema, packageFilterSchema, activationSchema, iccidValidationSchema, activationResponseSchema, paymentSchema, paymentIntentSchema, paymentStatusSchema, paymentMethodSchema, ticketSchema, createTicketSchema, createAdminUserSchema, updateAdminUserSchema.

Support schemas include customerCreateTicketSchema, staffCreateTicketSchema, ticketCommentSchema, staffTicketCommentSchema, assignTicketSchema, ticketStatusChangeSchema, officerTicketStatusSchema, createRefundRequestSchema, decideRefundSchema, customerTicketListQuerySchema, and staffTicketListQuerySchema. They enforce strict bodies, one-or-zero links for customer tickets, exactly one link for refunds, 5,000-character ticket text, 500-character reason text, UUID ids, and keyset page limits.

Officer schemas (schemas/officer.ts): officerResolveSchema (outcome, evidence, reason), officerQueueQuerySchema (cursor, limit, kind, and at most one of phone, a serial-shaped serial restricted to the activations tab, or a UUID orderId restricted to the store tab), officerCompletionSearchQuerySchema (exactly one of phone or serial; a phone with a leading trunk 0 is read as Saudi and normalised to E.164, a serial is 19 or 20 digits), and officerCreateTicketSchema (activationId, subject, description, reusing the support ticket subject and body schemas).

Store schemas (from schemas/store.ts): supportedCurrencySchema, moneySchema, storeProductSchema, adminStoreProductSchema, storeDestinationSchema, storeDataAllowanceSchema, storeValiditySchema, storeProductListResponseSchema, storeDestinationsResponseSchema, storeProductListQuerySchema, storeDestinationsQuerySchema, createStoreOrderRequestSchema, storeOrderResponseSchema, storeOrderViewSchema, adminStoreOrderViewSchema, storeOrderListQuerySchema, adminStoreOrderListQuerySchema, storeOrderListResponseSchema, adminStoreOrderListResponseSchema, adminStoreProductListQuerySchema, adminStoreProductListResponseSchema, updateStoreProductSchema, updateStoreProviderSchema, esimIssuanceStateSchema, providerCatalogItemSchema, storeProviderHealthSnapshotSchema, fxRateEntrySchema, fxRatesResponseSchema, storeProviderSchema, storeProvidersResponseSchema, deviceCompatibilityEntrySchema, deviceCompatibilityResponseSchema.

Notification Request and Response Schemas ​

Every schema below is exported from schemas/notification.ts through the package root.

SchemaContract
notificationChannelSchemain_app, email, sms, push.
notificationQueuedChannelSchemaemail, sms.
optOutEligibleChannelSchemaemail, sms, push.
notificationCategorySchemaactivation, payment, esim_delivery, company, admin, system, announcement.
notificationSeveritySchemainfo, warning, critical.
notificationUrgencySchemanormal, urgent.
notificationTemplateKeySchemaThe twelve registered event keys plus custom.
notificationEventKeySchemaThe twelve registered event keys.
notificationDeliveryOutcomeSchemaqueued, sending, sent, delivered, failed_terminal, suppressed.
notificationSuppressionReasonSchemaaccount_inactive, channel_opted_out, no_address, provider_unsupported.
notificationBatchStatusSchemascheduled, running, finished, cancelled.
notificationProviderSlugSchema1 to 64 characters. Starts with a lowercase letter or digit. Remaining characters may also include _ and -.
inboxFeedQuerySchemaOptional cursor, limit from 1 to 50 with default 20, unreadOnly with default false, and archived with default false. Accepts query strings true and false for both booleans.
inboxNotificationSchemaUUID id, eventKey, category, template key, nullable severity, rendered title and body, nullable action and image URLs, degraded flag for a failed render, nullable batch ID, creation time, and nullable read, seen, expiry and archive times. Timestamps must be RFC 3339.
inboxFeedResponseSchemadata, nullable nextCursor and hasMore. No total.
unreadNotificationCountResponseSchemaNonnegative integer count.
notificationIdParamSchemaUUID id.
markNotificationReadResponseSchemaUUID id and readAt.
markAllNotificationsReadResponseSchemaNonnegative integer updated.
markNotificationsSeenSchema1 to 200 UUIDs in notificationIds. Removes duplicates after the input bound is checked.
markNotificationsSeenResponseSchemaNonnegative integer updated.
archiveNotificationResponseSchemaUUID id and archivedAt.
notificationChannelStateSchemaChannel id, bilingual label, enabled and optOutAllowed.
notificationOptOutsResponseSchemaChannel states in channels and the user's eligible channel list in optOuts.
updateNotificationOptOutsSchemaFull replacement channels list. Removes duplicates. An empty list clears opt-outs. Rejects in_app.
notificationAudienceSchemaStrict kind branches. users requires 1 to 1000 UUIDs. company requires a UUID companyId. role requires a UserRole. all takes no other fields.
adminSendNotificationSchemaStrict union requiring exactly one of content or template. Requires category, audience and at least one channel. Urgency defaults to normal. Severity is optional. Schedule and expiry accept null or offset-bearing timestamps. A supplied schedule must precede a supplied expiry.
adminSendNotificationResponseSchemabatchId, status, recipientsTotal, deliveriesQueued, deliveriesSuppressed, fullyEnqueued and nullable scheduledAt. Reports acceptance, not delivery.
adminNotificationBatchListQuerySchemaOptional cursor, status and category. limit ranges from 1 to 100 with default 20.
adminNotificationBatchSummarySchemaid, category, status, recipientsTotal, counts, nullable createdBy, nullable createdByName, createdAt and nullable scheduled, started, finished and cancelled times.
adminNotificationBatchListResponseSchemadata, page-length total, nullable nextCursor and hasMore.
notificationBatchCountsSchemaNonnegative integer sent, delivered, failed, suppressed and pending counts.
notificationDeliveryFailureSchemaDelivery and notification IDs, channel, outcome, nullable provider, attempts, nullable error code and text, nullable suppression reason and nullable failure time.
adminNotificationBatchDetailResponseSchemabatch, aggregate counts and a separate failures page with data, total, nullable nextCursor and hasMore.
cancelNotificationBatchResponseSchemaUUID id, status and nullable cancelledAt.
deliveryRateQuerySchemaOffset-bearing from and to, with from before to. Optional channel and provider slug. The window applies to delivery creation time.
deliveryRateBucketSchemaChannel, nullable provider, nonnegative total, sent, delivered, failed, suppressed and pending counts, and a rate from 0 to 1.
deliveryRateFailureReasonSchemaChannel, nullable provider, reason and nonnegative integer count.
deliveryRateResponseSchemafrom, to, buckets and failureReasons.
adminNotificationRecipientSearchQuerySchemaq trimmed, up to 100 characters, defaulting to empty. A company search needs at least 2 characters. A member or staff search may leave q empty, which lists the set. kind of company, member or staff, and optional UUID companyId, required only when kind is member.
adminNotificationRecipientSearchResponseSchemadata, an array of { id, name } rows. No contact details or role.
inboundNotificationWebhookParamsSchemaQueued channel and provider slug.

Inbox and batch cursors preserve the full stored creation timestamp and UUID, separated by |. They order by creation time and ID descending. The custom admin branch validates its content directly. The keyed branch accepts { key, params }, where key is an event key rather than custom. Event-specific params are validated through the event registry before persistence.

Inferred TypeSource Schema
InboxFeedQueryInputinboxFeedQuerySchema
MarkNotificationsSeenInputmarkNotificationsSeenSchema
UpdateNotificationOptOutsInputupdateNotificationOptOutsSchema
AdminSendNotificationInputadminSendNotificationSchema
AdminNotificationBatchListQueryInputadminNotificationBatchListQuerySchema
AdminNotificationRecipientSearchQueryInputadminNotificationRecipientSearchQuerySchema
DeliveryRateQueryInputdeliveryRateQuerySchema
InboundNotificationWebhookParamsinboundNotificationWebhookParamsSchema
CustomNotificationParamscustomNotificationParamsSchema

Notification Event Schemas ​

schemas/notification-events.ts exports these strict params schemas. UUID fields reject non-UUID values. Occurrence counters are nonnegative integers. Each event uses a template with the same key.

Event KeyParams SchemaRequired ParamsCategoryRegistered Channels
activation.completedactivationCompletedParamsSchemaUUID activationId, packageName from 1 to 255 charactersactivationin_app, email, sms, push
payment.succeededpaymentSucceededParamsSchemaUUID paymentId, UUID orderIdpaymentin_app, email, push
company.approvedcompanyApprovedParamsSchemaUUID companyId, UUID approvalAuditIdcompanyin_app, email, push
contract.readycontractReadyParamsSchemaUUID companyId, UUID contractId, readinessEpisode countercompanyin_app, email, push
admin.invitedadminInvitedParamsSchemaUUID userId, inviteSequence counteradminin_app, email, sms
esim.issuedesimIssuedParamsSchemasource: 'activation' with UUID activationId, or source: 'store' with UUID storeOrderIdesim_deliveryin_app, email, push
ticket.opened, ticket.assigned, ticket.replied, ticket.resolved, ticket.refund_requestedticketEventParamsSchemaUUID ticketId, reference matching TKT- plus 12 lowercase hex characterssystemin_app

NotificationEventEntry defines readonly category, template, channels and params schema fields. NOTIFICATION_EVENT_REGISTRY contains all twelve entries. getNotificationEvent(key) returns an entry. A registered push channel remains disabled by the channel registry.

SchemaContract
customNotificationParamsSchemaStrict object with bilingual title and body. Each language is trimmed and must remain nonempty. Titles allow 160 UTF-8 bytes per language. Bodies allow 1000 bytes per language. actionUrl and imageUrl are optional or null.
notificationUrlSchema1 to 2048 characters. Accepts a root-relative path that does not start with //, or a parsed URL with the https: protocol.

Constants ​

ts
SIM_LIMITS       // { citizen: 10, resident: 2, visitor: 1 }
QUEUE_NAMES      // activation, payment, store-fulfillment, notification-email, notification-sms, notification-orchestration
RETRY_CONFIG     // Per-queue retry configuration
ERROR_CODES      // All error code constants
STORE_SUPPORTED_CURRENCIES // ['USD', 'SAR', 'AED', 'EUR', 'GBP', 'EGP']
BASE_CURRENCY    // 'USD'
ESIM_ISSUANCE_STATES       // ['pending', 'in_flight', 'provider_order_created', 'completion_in_flight', 'issued', 'pending_verification', 'reconciliation_required', 'failed']
ESIM_PROVIDER_ERROR_CODES  // ['OUT_OF_STOCK', 'TRANSIENT_UNAVAILABLE', 'TERMINAL_FAILURE', 'AMBIGUOUS_RESULT']
STORE_TRANSITION_SOURCES   // ['system'] — the source recorded on store order status events (StoreTransitionSource)
ICCID_LENGTH     // 19
DEVICE_COMPATIBILITY       // Versioned list of eSIM-capable phone models
DEVICE_COMPATIBILITY_VERSION // Current version string of the device list
resolveDeviceCompatibility // Canonical resolver: exact beats prefix, unknown never blocks

Notification Constants ​

These exports are available from the package root. Vocabulary arrays and registry helpers live in types/notification.ts. Content bounds live in constants/notification.ts.

ExportValue or Purpose
NOTIFICATION_CHANNELSin_app, email, sms, push.
NOTIFICATION_CHANNEL_REGISTRYBilingual labels and enabled flags. In-app, email and SMS are enabled. Push is disabled.
NOTIFICATION_QUEUED_CHANNELSemail, sms.
OPT_OUT_ELIGIBLE_CHANNELSemail, sms, push.
NOTIFICATION_CATEGORIESactivation, payment, esim_delivery, company, admin, system, announcement.
NOTIFICATION_CATEGORY_REGISTRYBilingual labels and transactional flags. Only announcement is non-transactional.
NOTIFICATION_SEVERITIESinfo, warning, critical.
NOTIFICATION_URGENCIESnormal, urgent.
NOTIFICATION_EVENT_KEYSThe twelve registered activation, payment, company, admin, eSIM and ticket event keys.
CUSTOM_TEMPLATE_KEYcustom.
NOTIFICATION_TEMPLATE_KEYSactivation.completed, payment.succeeded, company.approved, contract.ready, admin.invited, esim.issued, custom.
NOTIFICATION_DELIVERY_OUTCOMESqueued, sending, sent, delivered, failed_terminal, suppressed.
NOTIFICATION_OUTCOME_PROGRESSIONqueued, sending, sent, delivered, in forward order.
TERMINAL_NOTIFICATION_OUTCOMESdelivered, failed_terminal, suppressed. sent is not terminal in the shared transition rules.
NOTIFICATION_SUPPRESSION_REASONSaccount_inactive, channel_opted_out, no_address, provider_unsupported.
NOTIFICATION_BATCH_STATUSESscheduled, running, finished, cancelled.
NOTIFICATION_AUDIENCE_KINDSusers, company, role, all.
NOTIFICATION_SEND_RESULTSaccepted, rejected_retryable, rejected_terminal, unsupported.
isChannelEnabled(channel)Reads the channel registry's enabled flag.
isTransactionalCategory(category)Reads the category registry's transactional flag.
isNotificationEventKey(value)Type guard for the six event keys.
isTerminalDeliveryOutcome(outcome)Tests membership in the terminal outcome array.
canAdvanceDeliveryOutcome(from, to)Checks the allowed transitions listed above.
NOTIFICATION_EVENT_REGISTRYEvent-to-category, template, channels and params schema mapping.
getNotificationEvent(key)Returns the registered event entry.
MAX_CUSTOM_TITLE_BYTES160 UTF-8 bytes per language.
MAX_CUSTOM_BODY_BYTES1000 UTF-8 bytes per language.
MAX_NOTIFICATION_URL_LENGTH2048 characters per URL.
MAX_EXPLICIT_RECIPIENTS1000 user IDs per explicit audience.
NOTIFICATION_RECIPIENT_CHUNK_SIZE500 recipients per processing chunk.
MAX_SEEN_NOTIFICATION_IDS200 IDs per mark-seen request.
RECIPIENT_SEARCH_LIMIT100 rows per recipient search. A member or staff search with no term is bounded to this cap in the database.
NOTIFICATION_URL_SCHEMES['https:'].
isAllowedNotificationUrl(value)Allows a root-relative path except //, or a parsed URL using an allowed protocol.
INBOX_MAX_LIMIT50. Exported from schemas/notification.ts.
BATCH_LIST_MAX_LIMIT100. Exported from schemas/notification.ts.
NOTIFICATION_ERROR_CODESThe ten notification codes listed under Error Classes.
NOTIFICATION_ERROR_MESSAGESEnglish and Arabic messages keyed by NotificationErrorCode.

Custom copy is stored per recipient. Byte limits apply separately to English and Arabic, not to their combined length.

Queue ConstantQueue NameRetry ConstantAttemptsBackoff
QUEUE_NAMES.notificationEmailnotification-emailRETRY_CONFIG.notificationEmail5Exponential, 5000 ms base
QUEUE_NAMES.notificationSmsnotification-smsRETRY_CONFIG.notificationSms3Exponential, 5000 ms base
QUEUE_NAMES.notificationOrchestrationnotification-orchestrationRETRY_CONFIG.notificationOrchestration5Exponential, 1000 ms base

Error Classes ​

All errors extend AppError with bilingual messages:

ts
import { ValidationError, NotFoundError } from '@activation-sys/shared';

throw new ValidationError('Validation failed', 'فشل التحقق', 'VALIDATION_ERROR', fieldErrors);
throw new NotFoundError('User not found', 'المستخدم غير موجود');

// Convert to API response (respects Accept-Language header)
toApiResponse(error, 'ar'); // Returns Arabic message
toApiResponse(error, 'en'); // Returns English message
ClassStatusUse Case
AppErrorCustomBase error class
AuthError401/403Authentication/authorization failures
ValidationError400Request validation failures
NotFoundError404Resource not found
EsimProviderError—Classified eSIM provider failure thrown by adapters (code + optional externalOrderId) — internal to the fulfillment pipeline, never an HTTP envelope
ActivationError422SIM activation failures
PaymentError402/409Payment processing failures
OperatorError502/504Operator API failures
NotificationError400/401/404/409Notification failures with status selected by NotificationErrorCode
SupportError403/409/422/503Ticket, refund, officer, and identity-start failures with status selected by SupportErrorCode
IdentityError400/409Identity number and identity type failures with status selected by IdentityErrorCode

Notification Errors ​

NotificationError(messageEn, messageAr, code, details?) accepts only a NotificationErrorCode. notificationError(code, details?) creates the same error using NOTIFICATION_ERROR_MESSAGES. Both work with toApiResponse. The status mapping is internal and is not exported.

CodeStatusMeaning
NOTIFICATION_NOT_FOUND404The notification does not exist or belongs to another user.
NOTIFICATION_BATCH_NOT_FOUND404The batch does not exist.
NOTIFICATION_BATCH_NOT_CANCELLABLE409The batch has started or finished and can no longer be cancelled.
NOTIFICATION_INVALID_CUSTOM_CONTENT400Custom content is invalid, exceeds its limits or contains a disallowed URL.
NOTIFICATION_UNKNOWN_EVENT400The event key has no registry entry.
NOTIFICATION_CHANNEL_DISABLED400The requested channel is declared but unavailable.
NOTIFICATION_OPT_OUT_NOT_ALLOWED400The channel cannot be muted.
NOTIFICATION_WEBHOOK_SIGNATURE_INVALID401The callback signature is invalid.
NOTIFICATION_WEBHOOK_PROVIDER_UNKNOWN404The callback provider is unknown or unavailable for the channel.
NOTIFICATION_IDEMPOTENCY_CONFLICT409The idempotency key was reused with a different request.

These ten codes also appear in ERROR_CODES. Route-specific operational failures can use AppError without joining this typed catalogue.

SupportError ​

SupportError(code, messageEn, messageAr, details?) uses a fixed status map. It covers ticket scope and state (TICKET_NOT_FOUND, TICKET_CLOSED, TICKET_LINK_INVALID, TICKET_ASSIGNEE_INVALID, TICKET_ACTION_NOT_ALLOWED), refund decisions (REFUND_ORDER_NOT_PAID, REFUND_NOT_DECIDABLE, REFUND_ALREADY_DECIDED, REFUND_SELF_DECISION, REFUND_DECIDER_CANNOT_OPEN — the refund decider role never opens a refund request, 403, REFUND_STORE_APPROVAL_DEFERRED), officer actions (OFFICER_CLAIM_TAKEN, OFFICER_CLAIM_NOT_HELD, OFFICER_ACTION_NOT_ALLOWED, OFFICER_JOB_IN_FLIGHT, OFFICER_COMPANY_READ_ONLY, OFFICER_LOOKUP_FAILED — a direct operator lookup call failed or timed out, 502), and the safe identity-start failure IDENTITY_START_UNAVAILABLE.

IdentityError ​

IdentityError(code, details?) uses a fixed status map and fixed bilingual copy per code, shared by the customer profile and the customer identity check. ID_NUMBER_INVALID and ID_NUMBER_TYPE_MISMATCH (400) refuse an identity number that does not fit the identity type it is checked against, or fits the other type instead. IDENTITY_TYPE_LOCKED (409) refuses an identity type change while an identity check for the customer is waiting or once one has completed. IDENTITY_TYPE_COMPANY_MANAGED (409) refuses a change for a company employee, whose type comes from the company's roster.

Internal documentation - Activation System