add-provider
Add a new notification provider (email, SMS, WhatsApp, push, voice) to the OsmoX API with service, consumer, queue wiring, and master provider seed.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Add a new notification provider (email, SMS, WhatsApp, push, voice) to the OsmoX API with service, consumer, queue wiring, and master provider seed.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Generate a complete CRUD or read-only list page in the Angular portal with service, routing, and menu integration. Use when adding a new feature page to the portal.
Regenerate TypeScript types from the backend OpenAPI spec for the portal. Use after adding or changing backend API endpoints, DTOs, or response shapes.
Run lint, format, and build checks across the monorepo. Use when asked to check code quality, fix lint errors, or verify the build passes.
Create a git commit following project conventions. Use when asked to commit changes.
Create, run, or revert TypeORM database migrations. Use when modifying entities, adding columns, creating tables, or troubleshooting database schema issues.
Sync source markdown docs to the Mintlify documentation site. Use when source docs in apps/api/docs/ are added or updated, or when creating new docs-site pages.
| name | add-provider |
| description | Add a new notification provider (email, SMS, WhatsApp, push, voice) to the OsmoX API with service, consumer, queue wiring, and master provider seed. |
Add a new notification provider $ARGUMENTS to the OsmoX API.
Ask the user the following questions using AskUserQuestion:
Q1 — Provider name and channel type:
mailgun, sms-twilio, wa-twilio-business)Q2 — SDK / transport library:
nodemailer, @mailgun/mailgun.js, twilio)Q3 — Configuration fields:
FIELD_NAME: "description" (type: string|number, pattern: "regex")Example:API_KEY: "Provider API key" (type: string, pattern: "^.{10,}$")
Q4 — Delivery confirmation:
Q5 — Notification data shape:
data object contain? (e.g., to, from, subject, body, html)Read these files to match exact patterns:
apps/api/src/modules/providers/smtp/smtp.service.ts and smtp.module.tsapps/api/src/modules/providers/sms-twilio/sms-twilio.service.tsapps/api/src/modules/providers/wa-twilio/wa-twilio.service.tsapps/api/src/jobs/consumers/notifications/smtp-notifications.job.consumer.ts (skip confirmation)apps/api/src/jobs/consumers/notifications/mailgun-notifications.job.consumer.ts (with confirmation)apps/api/src/modules/notifications/notifications.module.tsapps/api/src/modules/notifications/queues/queue.service.tsapps/api/src/common/constants/notifications.tsapps/api/src/database/migrations/1745495895857-InitialSeed.ts (master providers seed)If the provider requires an npm package:
cd apps/api && npm install <package-name>
Run from apps/api/:
cd apps/api
nest generate module modules/providers/<provider-name>
nest generate service modules/providers/<provider-name> --no-spec
This creates the module and service files with correct boilerplate. Then replace the generated content with the patterns below.
Replace the generated service at apps/api/src/modules/providers/<provider-name>/<provider-name>.service.ts:
import { Injectable, Logger } from '@nestjs/common';
import { ProvidersService } from '../providers.service';
@Injectable()
export class <ProviderName>Service {
private <clientVar>; // SDK client instance
constructor(
private readonly providersService: ProvidersService,
private logger: Logger = new Logger(<ProviderName>Service.name),
) {}
async assignTransport(providerId: number): Promise<void> {
this.logger.debug('Assigning transport for <ProviderName>');
const config = await this.providersService.getConfigById(providerId);
// Initialize SDK client with config values
// e.g., this.<clientVar> = new Client({ apiKey: config.API_KEY });
}
async sendMessage(notificationData: <DataType>, providerId: number): Promise<unknown> {
await this.assignTransport(providerId);
this.logger.debug('Sending <ProviderName> notification');
// Call SDK send method
// return result;
}
// Only if provider requires delivery confirmation:
async getDeliveryStatus(messageId: string, providerId: number): Promise<unknown> {
await this.assignTransport(providerId);
this.logger.debug('Fetching delivery status from <ProviderName>');
// Call SDK status method
// return status;
}
}
Replace the generated module at apps/api/src/modules/providers/<provider-name>/<provider-name>.module.ts:
import { Logger, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { <ProviderName>Service } from './<provider-name>.service';
import { ProvidersModule } from '../providers.module';
import { ProvidersService } from '../providers.service';
@Module({
imports: [ConfigModule, ProvidersModule],
providers: [<ProviderName>Service, ProvidersService, Logger],
exports: [<ProviderName>Service],
})
export class <ProviderName>Module {}
Important: nest generate module will auto-add the new module to app.module.ts imports. Remove that auto-added import — the provider module should only be imported by notifications.module.ts, not the root module.
Consumers are not NestJS modules/services — they're plain injectable classes that extend NotificationConsumer. Create the file manually at apps/api/src/jobs/consumers/notifications/<provider-name>-notifications.job.consumer.ts:
import { Inject, Injectable, forwardRef } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ConfigService } from '@nestjs/config';
import { NotificationConsumer } from './notification.consumer';
import { Notification } from 'src/modules/notifications/entities/notification.entity';
import { RetryNotification } from 'src/modules/notifications/entities/retry-notification.entity';
import { NotificationsService } from 'src/modules/notifications/notifications.service';
import { NotificationQueueProducer } from 'src/jobs/producers/notifications/notifications.job.producer';
import { WebhookService } from 'src/modules/webhook/webhook.service';
import { ProviderChainMembersService } from 'src/modules/provider-chain-members/provider-chain-members.service';
import { ProvidersService } from 'src/modules/providers/providers.service';
import { <ProviderName>Service } from 'src/modules/providers/<provider-name>/<provider-name>.service';
@Injectable()
export class <ProviderName>NotificationConsumer extends NotificationConsumer {
constructor(
@InjectRepository(Notification)
protected readonly notificationRepository: Repository<Notification>,
@InjectRepository(RetryNotification)
protected readonly notificationRetryRepository: Repository<RetryNotification>,
private readonly <providerCamelCase>Service: <ProviderName>Service,
@Inject(forwardRef(() => NotificationsService))
notificationsService: NotificationsService,
@Inject(forwardRef(() => NotificationQueueProducer))
notificationsQueueService: NotificationQueueProducer,
webhookService: WebhookService,
configService: ConfigService,
providerChainMembersService: ProviderChainMembersService,
providersService: ProvidersService,
) {
super(
notificationRepository,
notificationRetryRepository,
notificationsService,
notificationsQueueService,
webhookService,
configService,
providerChainMembersService,
providersService,
);
}
async process<ProviderName>NotificationQueue(id: number): Promise<void> {
return super.processNotificationQueue(id, async () => {
const notification = (await this.notificationsService.getNotificationById(id))[0];
return this.<providerCamelCase>Service.sendMessage(
notification.data as <DataType>,
notification.providerId,
);
});
}
// Only if provider requires delivery confirmation:
async process<ProviderName>NotificationConfirmationQueue(id: number): Promise<void> {
return super.processAwaitingConfirmationNotificationQueue(id, async () => {
const notification = (await this.notificationsService.getNotificationById(id))[0];
const sendResult = notification.result.result as <SendResultType>;
const status = await this.<providerCamelCase>Service.getDeliveryStatus(
sendResult.<messageIdField>,
notification.providerId,
);
// Map provider status to DeliveryStatus enum
// Return { result, deliveryStatus }
});
}
}
notifications.module.ts)Add three things:
providerModules array and consumer to consumers arrayqueue.service.ts)Add three things:
case statements in the createWorker switch:case `${QueueAction.SEND}-${ChannelType.<CHANNEL_TYPE>}`:
await this.<providerCamelCase>NotificationConsumer.process<ProviderName>NotificationQueue(job.data.id);
break;
// If confirmation required:
case `${QueueAction.DELIVERY_STATUS}-${ChannelType.<CHANNEL_TYPE>}`:
await this.<providerCamelCase>NotificationConsumer.process<ProviderName>NotificationConfirmationQueue(job.data.id);
break;
notifications.ts)Add to:
ChannelType — only if this is a new channel type (not already existing)RecipientKeyForChannelType — map channel type to data field key (e.g., 'to', 'target')SkipProviderConfirmationChannels — add channel type if no confirmation neededProviderDeliveryStatus — add success/failure states if confirmation is neededAdd entry to the masterProvidersData array in apps/api/src/database/migrations/1745495895857-InitialSeed.ts:
{
name: '<PROVIDER_DISPLAY_NAME>',
provider_type: <channel_type_number>,
configuration: {
CONFIG_KEY: {
label: 'Human Readable Label',
id: 'CONFIG_KEY',
pattern: '^validation-regex$',
type: 'string',
},
// ... more config fields
},
},
Important: The configuration keys must match exactly what the provider service reads via this.providersService.getConfigById(providerId).
cd apps/api && npm run build — must succeedcd apps/api && npm run lint — must passcd apps/api && npm run typeorm:run-migration