feat(GRO-984): persist outbound SMS to messages table

Wire sendSms() to find/create the conversation by sender/recipient
and insert an outbound message row with the Telnyx message_id.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-05-04 02:44:59 +00:00
committed by Flea Flicker [agent]
parent c79b5220a4
commit 701889c06f
+77 -1
View File
@@ -1,5 +1,7 @@
import { Telnyx } from "telnyx";
import { createHmac } from "crypto";
import { v4 as uuidv4 } from "uuid";
import { getDb, conversations, messages, businessSettings, eq, and } from "@groombook/db";
export interface SmsProvider {
sendSms(to: string, body: string, mediaUrls?: string[]): Promise<{ messageId: string; status: string }>;
@@ -32,6 +34,48 @@ function isE164(phone: string): boolean {
return /^\+[1-9]\d{7,14}$/.test(phone);
}
async function findOrCreateConversationForOutbound(
businessId: string,
clientPhone: string,
businessNumber: string
): Promise<{ id: string; clientId: string }> {
const db = getDb();
const [existing] = await db
.select({ id: conversations.id, clientId: conversations.clientId })
.from(conversations)
.where(
and(
eq(conversations.businessId, businessId),
eq(conversations.externalNumber, clientPhone),
eq(conversations.businessNumber, businessNumber)
)
)
.limit(1);
if (existing) {
return { id: existing.id, clientId: existing.clientId };
}
const clientId = uuidv4();
const [created] = await db
.insert(conversations)
.values({
id: uuidv4(),
businessId,
clientId,
channel: "sms",
externalNumber: clientPhone,
businessNumber,
lastMessageAt: new Date(),
status: "active",
})
.returning({ id: conversations.id, clientId: conversations.clientId });
return { id: created.id, clientId: created.clientId };
}
export async function sendSms(
to: string,
body: string,
@@ -58,6 +102,38 @@ export async function sendSms(
const result = await client.messages.create(payload as Record<string, string | string[]>);
const smsResult = result.data as unknown as TelnyxSmsResult;
const db = getDb();
const [settings] = await db
.select({ id: businessSettings.id })
.from(businessSettings)
.where(eq(businessSettings.messagingPhoneNumber, from))
.limit(1);
if (settings?.id) {
const { id: conversationId } = await findOrCreateConversationForOutbound(
settings.id,
to,
from
);
await db
.update(conversations)
.set({ lastMessageAt: new Date(), updatedAt: new Date() })
.where(eq(conversations.id, conversationId));
await db
.insert(messages)
.values({
id: uuidv4(),
conversationId,
direction: "outbound",
body,
status: "sent",
providerMessageId: smsResult.message_id,
});
}
return {
messageId: smsResult.message_id,
status: smsResult.status,
@@ -139,4 +215,4 @@ export async function smsSend(
await provider.sendSms(to, body, mediaUrls);
return true;
}
}