feat: log outbound SMS and show history in compose modal

Persist manual, campaign, and automation SMS; expose history APIs for people and leads.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-17 21:23:07 +03:30
co-authored by Cursor
parent 9325d879a5
commit 23f8da2c65
16 changed files with 457 additions and 3 deletions
@@ -15,6 +15,7 @@ import { PrismaService } from '../prisma/prisma.service';
import { NotificationsService } from '../notifications/notifications.service';
import { SmsService } from '../common/sms.service';
import { dateToJalali, jalaliMonthName } from '../common/jalali';
import { OutboundSmsLogService } from '../outbound-sms/outbound-sms-log.service';
import {
applyMessageTemplate,
buildSendAts,
@@ -47,6 +48,7 @@ export class MessageAutomationsService {
private readonly prisma: PrismaService,
private readonly notifications: NotificationsService,
private readonly sms: SmsService,
private readonly outboundSms: OutboundSmsLogService,
) {}
async list(organizationId: string): Promise<MessageAutomationRuleDto[]> {
@@ -328,7 +330,9 @@ export class MessageAutomationsService {
personId: string | null;
ownerId: string | null;
assigneeId: string | null;
ruleId: string;
rule: {
id?: string;
title: string;
body: string;
channel: string;
@@ -398,11 +402,23 @@ export class MessageAutomationsService {
}
if (targetPerson && wantSms) {
await this.sendSms(person?.mobile || person?.phone, smsText);
await this.sendSms(person?.mobile || person?.phone, smsText, {
organizationId: job.organizationId,
personId: job.personId,
ruleId: job.ruleId || job.rule.id || '',
});
}
}
private async sendSms(mobile: string | null | undefined, text: string) {
private async sendSms(
mobile: string | null | undefined,
text: string,
log?: {
organizationId: string;
personId?: string | null;
ruleId: string;
},
) {
if (!mobile) {
this.logger.warn('Message automation SMS skipped: no mobile');
return;
@@ -412,6 +428,18 @@ export class MessageAutomationsService {
return;
}
const result = await this.sms.sendSmsDetailed(mobile, text);
if (log?.personId) {
await this.outboundSms.record({
organizationId: log.organizationId,
personId: log.personId,
mobile,
body: text,
source: 'automation',
sourceId: log.ruleId,
status: result.ok ? 'sent' : 'failed',
error: result.ok ? null : result.reason,
});
}
if (!result.ok) {
this.logger.warn(
`Message automation SMS failed: ${result.reason ?? 'unknown'}`,