chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
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 })
|
||||
}
|
||||
Reference in New Issue
Block a user