Files
simstudioai--sim/apps/sim/lib/mothership/inbox/lifecycle.ts
T
wehub-resource-sync d25d482dc2
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

145 lines
4.1 KiB
TypeScript

import { db, mothershipInboxWebhook, workspace } from '@sim/db'
import { createLogger } from '@sim/logger'
import { generateId } from '@sim/utils/id'
import { eq } from 'drizzle-orm'
import { getBaseUrl } from '@/lib/core/utils/urls'
import * as agentmail from '@/lib/mothership/inbox/agentmail-client'
import type { InboxConfig } from '@/lib/mothership/inbox/types'
const logger = createLogger('InboxLifecycle')
/**
* Enable inbox for a workspace:
* 1. Create AgentMail inbox (with optional custom username)
* 2. Create AgentMail webhook scoped to this inbox
* 3. Store inbox details + webhook secret in DB
* 4. Update workspace.inboxEnabled = true
*/
export async function enableInbox(
workspaceId: string,
opts?: { username?: string }
): Promise<InboxConfig> {
const inbox = await agentmail.createInbox({
username: opts?.username,
displayName: 'Sim',
})
logger.info('AgentMail createInbox response', { inbox: JSON.stringify(inbox) })
if (!inbox?.inbox_id) {
throw new Error('AgentMail createInbox response missing inbox_id')
}
let webhook: Awaited<ReturnType<typeof agentmail.createWebhook>> | null = null
try {
webhook = await agentmail.createWebhook({
url: `${getBaseUrl()}/api/webhooks/agentmail`,
eventTypes: ['message.received'],
inboxIds: [inbox.inbox_id],
})
await db.insert(mothershipInboxWebhook).values({
id: generateId(),
workspaceId,
webhookId: webhook.webhook_id,
secret: webhook.secret,
})
await db
.update(workspace)
.set({
inboxEnabled: true,
inboxAddress: inbox.inbox_id,
inboxProviderId: inbox.inbox_id,
updatedAt: new Date(),
})
.where(eq(workspace.id, workspaceId))
logger.info('Inbox enabled', { workspaceId, address: inbox.inbox_id })
return {
enabled: true,
address: inbox.inbox_id,
providerId: inbox.inbox_id,
}
} catch (error) {
try {
if (webhook) await agentmail.deleteWebhook(webhook.webhook_id)
await agentmail.deleteInbox(inbox.inbox_id)
await db
.delete(mothershipInboxWebhook)
.where(eq(mothershipInboxWebhook.workspaceId, workspaceId))
} catch (rollbackError) {
logger.error('Failed to rollback AgentMail resources', { rollbackError })
}
throw error
}
}
/**
* Disable inbox:
* 1. Delete AgentMail webhook
* 2. Delete AgentMail inbox
* 3. Clear workspace inbox columns
* 4. Delete mothershipInboxWebhook row
*/
export async function disableInbox(workspaceId: string): Promise<void> {
const [[ws], [webhookRow]] = await Promise.all([
db
.select({ inboxProviderId: workspace.inboxProviderId })
.from(workspace)
.where(eq(workspace.id, workspaceId))
.limit(1),
db
.select({ webhookId: mothershipInboxWebhook.webhookId })
.from(mothershipInboxWebhook)
.where(eq(mothershipInboxWebhook.workspaceId, workspaceId))
.limit(1),
])
const deletePromises: Promise<void>[] = []
if (webhookRow) {
deletePromises.push(
agentmail.deleteWebhook(webhookRow.webhookId).catch((error) => {
logger.warn('Failed to delete AgentMail webhook', { error })
})
)
}
if (ws?.inboxProviderId) {
deletePromises.push(
agentmail.deleteInbox(ws.inboxProviderId).catch((error) => {
logger.warn('Failed to delete AgentMail inbox', { error })
})
)
}
await Promise.all(deletePromises)
await Promise.all([
db.delete(mothershipInboxWebhook).where(eq(mothershipInboxWebhook.workspaceId, workspaceId)),
db
.update(workspace)
.set({
inboxEnabled: false,
inboxAddress: null,
inboxProviderId: null,
updatedAt: new Date(),
})
.where(eq(workspace.id, workspaceId)),
])
logger.info('Inbox disabled', { workspaceId })
}
/**
* Update inbox address (regenerate):
* 1. Disable old inbox
* 2. Enable new inbox with new username
*/
export async function updateInboxAddress(
workspaceId: string,
newUsername: string
): Promise<InboxConfig> {
await disableInbox(workspaceId)
return enableInbox(workspaceId, { username: newUsername })
}