Repository: simstudioai/sim
Pull request #412: feat(teams-attachments): added support for teams attachments
Author: login: aadamgough
Created: 2025-05-25T04:50:33Z | Merged: 2025-05-25T05:28:31Z

## Description

Added support for teams attachments.

## Type of change

- [x] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

Ran the tools and saw the attachments there.

## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] All tests pass locally and in CI (`bun run test`)
- [x] My changes generate no new warnings
- [x] Any dependent changes have been merged and published in downstream modules
- [x] I have updated version numbers as needed (if needed)
- [x] I confirm that I have read and agree to the terms outlined in the [Contributor License Agreement (CLA)](./CONTRIBUTING.md#contributor-license-agreement-cla)

## Security Considerations:

- [x] My changes do not introduce any new security vulnerabilities
- [x] I have considered the security implications of my changes



Diff:
diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-selector/components/teams-message-selector.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-selector/components/teams-message-selector.tsx
index cf0a6f700cd..fc457c49ace 100644
--- a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-selector/components/teams-message-selector.tsx
+++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-selector/components/teams-message-selector.tsx
@@ -381,8 +381,8 @@ export function TeamsMessageSelector({
     setSelectedChannelId(channel.channelId || '')
     setSelectedChatId('')
     setSelectedMessage(channel)
-    setSelectedMessageId(channel.id)
-    onChange(channel.id, channel)
+    setSelectedMessageId(channel.channelId || '')
+    onChange(channel.channelId || '', channel)
     onMessageInfoChange?.(channel)
     setOpen(false)
   }
diff --git a/apps/sim/tools/microsoft_teams/attachment-utils.ts b/apps/sim/tools/microsoft_teams/attachment-utils.ts
new file mode 100644
index 00000000000..b8f259a7e31
--- /dev/null
+++ b/apps/sim/tools/microsoft_teams/attachment-utils.ts
@@ -0,0 +1,29 @@
+import type { MicrosoftTeamsAttachment } from './types'
+
+/**
+ * Transform raw attachment data from Microsoft Graph API
+ */
+function transformAttachment(rawAttachment: any): MicrosoftTeamsAttachment {
+  return {
+    id: rawAttachment.id,
+    contentType: rawAttachment.contentType,
+    contentUrl: rawAttachment.contentUrl,
+    content: rawAttachment.content,
+    name: rawAttachment.name,
+    thumbnailUrl: rawAttachment.thumbnailUrl,
+    size: rawAttachment.size,
+    sourceUrl: rawAttachment.sourceUrl,
+    providerType: rawAttachment.providerType,
+    item: rawAttachment.item,
+  }
+}
+
+/**
+ * Extract attachments from message data
+ * Returns all attachments without any content processing
+ */
+export function extractMessageAttachments(message: any): MicrosoftTeamsAttachment[] {
+  const attachments = (message.attachments || []).map(transformAttachment)
+
+  return attachments
+}
diff --git a/apps/sim/tools/microsoft_teams/read_channel.ts b/apps/sim/tools/microsoft_teams/read_channel.ts
index e08244b93a1..3ff2a4cad1b 100644
--- a/apps/sim/tools/microsoft_teams/read_channel.ts
+++ b/apps/sim/tools/microsoft_teams/read_channel.ts
@@ -1,11 +1,15 @@
+import { createLogger } from '@/lib/logs/console-logger'
 import type { ToolConfig } from '../types'
+import { extractMessageAttachments } from './attachment-utils'
 import type { MicrosoftTeamsReadResponse, MicrosoftTeamsToolParams } from './types'
 
+const logger = createLogger('MicrosoftTeamsReadChannel')
+
 export const readChannelTool: ToolConfig<MicrosoftTeamsToolParams, MicrosoftTeamsReadResponse> = {
   id: 'microsoft_teams_read_channel',
   name: 'Read Microsoft Teams Channel',
   description: 'Read content from a Microsoft Teams channel',
-  version: '1.0',
+  version: '1.1',
   oauth: {
     required: true,
     provider: 'microsoft-teams',
@@ -60,10 +64,16 @@ export const readChannelTool: ToolConfig<MicrosoftTeamsToolParams, MicrosoftTeam
       }
     },
   },
-  transformResponse: async (response: Response) => {
+  transformResponse: async (response: Response, params?: MicrosoftTeamsToolParams) => {
     if (!response.ok) {
       const errorText = await response.text()
-      throw new Error(`Failed to read Microsoft Teams channel: ${errorText}`)
+      logger.error(
+        `Microsoft Teams channel API error: ${response.status} ${response.statusText}`,
+        errorText
+      )
+      throw new Error(
+        `Failed to read Microsoft Teams channel: ${response.status} ${response.statusText} - ${errorText}`
+      )
     }
 
     const data = await response.json()
@@ -81,36 +91,88 @@ export const readChannelTool: ToolConfig<MicrosoftTeamsToolParams, MicrosoftTeam
             channelId: '',
             messageCount: 0,
             messages: [],
+            totalAttachments: 0,
+            attachmentTypes: [],
           },
         },
       }
     }
 
-    // Format the messages into a readable text
-    const formattedMessages = messages
-      .map((message: any) => {
+    if (!params?.teamId || !params?.channelId) {
+      throw new Error('Missing required parameters: teamId and channelId')
+    }
+    // Process messages with attachments
+    const processedMessages = messages.map((message: any, index: number) => {
+      try {
         const content = message.body?.content || 'No content'
-        const sender = message.from?.user?.displayName || 'Unknown sender'
-        const timestamp = message.createdDateTime
-          ? new Date(message.createdDateTime).toLocaleString()
+        const messageId = message.id
+
+        const attachments = extractMessageAttachments(message)
+
+        let sender = 'Unknown'
+        if (message.from?.user?.displayName) {
+          sender = message.from.user.displayName
+        } else if (message.messageType === 'systemEventMessage') {
+          sender = 'System'
+        }
+
+        return {
+          id: messageId,
+          content: content,
+          sender,
+          timestamp: message.createdDateTime,
+          messageType: message.messageType || 'message',
+          attachments,
+        }
+      } catch (error) {
+        logger.error(`Error processing message at index ${index}:`, error)
+        return {
+          id: message.id || `unknown-${index}`,
+          content: 'Error processing message',
+          sender: 'Unknown',
+          timestamp: message.createdDateTime || new Date().toISOString(),
+          messageType: 'error',
+          attachments: [],
+        }
+      }
+    })
+
+    // Format the messages into a readable text (no attachment info in content)
+    const formattedMessages = processedMessages
+      .map((message: any) => {
+        const sender = message.sender
+        const timestamp = message.timestamp
+          ? new Date(message.timestamp).toLocaleString()
           : 'Unknown time'
 
-        return `[${timestamp}] ${sender}: ${content}`
+        return `[${timestamp}] ${sender}: ${message.content}`
       })
       .join('\n\n')
 
+    // Calculate attachment statistics
+    const allAttachments = processedMessages.flatMap((msg: any) => msg.attachments || [])
+    const attachmentTypes: string[] = []
+    const seenTypes = new Set<string>()
+
+    allAttachments.forEach((att: any) => {
+      if (
+        att.contentType &&
+        typeof att.contentType === 'string' &&
+        !seenTypes.has(att.contentType)
+      ) {
+        attachmentTypes.push(att.contentType)
+        seenTypes.add(att.contentType)
+      }
+    })
+
     // Create document metadata
     const metadata = {
-      teamId: messages[0]?.channelIdentity?.teamId || '',
-      channelId: messages[0]?.channelIdentity?.channelId || '',
+      teamId: messages[0]?.channelIdentity?.teamId || params.teamId || '',
+      channelId: messages[0]?.channelIdentity?.channelId || params.channelId || '',
       messageCount: messages.length,
-      messages: messages.map((msg: any) => ({
-        id: msg.id,
-        content: msg.body?.content || '',
-        sender: msg.from?.user?.displayName || 'Unknown',
-        timestamp: msg.createdDateTime,
-        messageType: msg.messageType || 'message',
-      })),
+      totalAttachments: allAttachments.length,
+      attachmentTypes,
+      messages: processedMessages,
     }
 
     return {
diff --git a/apps/sim/tools/microsoft_teams/read_chat.ts b/apps/sim/tools/microsoft_teams/read_chat.ts
index e9a6fabb575..451fdb527f9 100644
--- a/apps/sim/tools/microsoft_teams/read_chat.ts
+++ b/apps/sim/tools/microsoft_teams/read_chat.ts
@@ -1,11 +1,12 @@
 import type { ToolConfig } from '../types'
+import { extractMessageAttachments } from './attachment-utils'
 import type { MicrosoftTeamsReadResponse, MicrosoftTeamsToolParams } from './types'
 
 export const readChatTool: ToolConfig<MicrosoftTeamsToolParams, MicrosoftTeamsReadResponse> = {
   id: 'microsoft_teams_read_chat',
   name: 'Read Microsoft Teams Chat',
   description: 'Read content from a Microsoft Teams chat',
-  version: '1.0',
+  version: '1.1',
   oauth: {
     required: true,
     provider: 'microsoft-teams',
@@ -44,7 +45,7 @@ export const readChatTool: ToolConfig<MicrosoftTeamsToolParams, MicrosoftTeamsRe
       }
     },
   },
-  transformResponse: async (response: Response) => {
+  transformResponse: async (response: Response, params?: MicrosoftTeamsToolParams) => {
     if (!response.ok) {
       const errorText = await response.text()
       throw new Error(`Failed to read Microsoft Teams chat: ${errorText}`)
@@ -64,35 +65,70 @@ export const readChatTool: ToolConfig<MicrosoftTeamsToolParams, MicrosoftTeamsRe
             chatId: '',
             messageCount: 0,
             messages: [],
+            totalAttachments: 0,
+            attachmentTypes: [],
           },
         },
       }
     }
 
-    // Format the messages into a readable text
-    const formattedMessages = messages
+    if (!params?.chatId) {
+      throw new Error('Missing required parameter: chatId')
+    }
+
+    // Process messages with attachments
+    const processedMessages = messages.map((message: any) => {
+      const content = message.body?.content || 'No content'
+      const messageId = message.id
+
+      // Extract attachments without any content processing
+      const attachments = extractMessageAttachments(message)
+
+      return {
+        id: messageId,
+        content: content, // Keep original content without modification
+        sender: message.from?.user?.displayName || 'Unknown',
+        timestamp: message.createdDateTime,
+        messageType: message.messageType || 'message',
+        attachments, // Attachments only stored here
+      }
+    })
+
+    // Format the messages into a readable text (no attachment info in content)
+    const formattedMessages = processedMessages
       .map((message: any) => {
-        const content = message.body?.content || 'No content'
-        const sender = message.from?.user?.displayName || 'Unknown sender'
-        const timestamp = message.createdDateTime
-          ? new Date(message.createdDateTime).toLocaleString()
+        const sender = message.sender
+        const timestamp = message.timestamp
+          ? new Date(message.timestamp).toLocaleString()
           : 'Unknown time'
 
-        return `[${timestamp}] ${sender}: ${content}`
+        return `[${timestamp}] ${sender}: ${message.content}`
       })
       .join('\n\n')
 
+    // Calculate attachment statistics
+    const allAttachments = processedMessages.flatMap((msg: any) => msg.attachments || [])
+    const attachmentTypes: string[] = []
+    const seenTypes = new Set<string>()
+
+    allAttachments.forEach((att: any) => {
+      if (
+        att.contentType &&
+        typeof att.contentType === 'string' &&
+        !seenTypes.has(att.contentType)
+      ) {
+        attachmentTypes.push(att.contentType)
+        seenTypes.add(att.contentType)
+      }
+    })
+
     // Create document metadata
     const metadata = {
-      chatId: messages[0]?.chatId || '',
+      chatId: messages[0]?.chatId || params.chatId || '',
       messageCount: messages.length,
-      messages: messages.map((msg: any) => ({
-        id: msg.id,
-        content: msg.body?.content || '',
-        sender: msg.from?.user?.displayName || 'Unknown',
-        timestamp: msg.createdDateTime,
-        messageType: msg.messageType || 'message',
-      })),
+      totalAttachments: allAttachments.length,
+      attachmentTypes,
+      messages: processedMessages,
     }
 
     return {
diff --git a/apps/sim/tools/microsoft_teams/types.ts b/apps/sim/tools/microsoft_teams/types.ts
index 0378889e7b2..e0408c554fb 100644
--- a/apps/sim/tools/microsoft_teams/types.ts
+++ b/apps/sim/tools/microsoft_teams/types.ts
@@ -1,5 +1,18 @@
 import type { ToolResponse } from '../types'
 
+export interface MicrosoftTeamsAttachment {
+  id: string
+  contentType: string
+  contentUrl?: string
+  content?: string
+  name?: string
+  thumbnailUrl?: string
+  size?: number
+  sourceUrl?: string
+  providerType?: string
+  item?: any
+}
+
 export interface MicrosoftTeamsMetadata {
   messageId?: string
   channelId?: string
@@ -7,7 +20,6 @@ export interface MicrosoftTeamsMetadata {
   chatId?: string
   content?: string
   createdTime?: string
-  modifiedTime?: string
   url?: string
   messageCount?: number
   messages?: Array<{
@@ -16,7 +28,11 @@ export interface MicrosoftTeamsMetadata {
     sender: string
     timestamp: string
     messageType: string
+    attachments?: MicrosoftTeamsAttachment[]
   }>
+  // Global attachments summary
+  totalAttachments?: number
+  attachmentTypes?: string[]
 }
 
 export interface MicrosoftTeamsReadResponse extends ToolResponse {
diff --git a/apps/sim/tools/microsoft_teams/write_channel.ts b/apps/sim/tools/microsoft_teams/write_channel.ts
index d6074de23df..78243bc7dce 100644
--- a/apps/sim/tools/microsoft_teams/write_channel.ts
+++ b/apps/sim/tools/microsoft_teams/write_channel.ts
@@ -44,11 +44,9 @@ export const writeChannelTool: ToolConfig<MicrosoftTeamsToolParams, MicrosoftTea
         throw new Error('Channel ID is required')
       }
 
-      // URL encode the IDs to handle special characters
       const encodedTeamId = encodeURIComponent(teamId)
       const encodedChannelId = encodeURIComponent(channelId)
 
-      // Send a message to a channel
       const url = `https://graph.microsoft.com/v1.0/teams/${encodedTeamId}/channels/${encodedChannelId}/messages`
 
       return url


Reviews:
user: login: greptile-apps[bot]
body: 
## PR Summary

This PR adds comprehensive support for Microsoft Teams attachments across the platform, enabling attachment handling in both chat and channel contexts.

- New `attachment-utils.ts` introduces `transformAttachment` and `extractMessageAttachments` functions but uses unvalidated `any` types that should be properly typed
- `read_chat.ts` and `read_channel.ts` need error handling for malformed attachment data and validation of attachment URLs for security
- `MicrosoftTeamsAttachment` interface in `types.ts` has several optional fields that could benefit from stricter typing
- `TeamsMessageSelector` component uses channelId for message identification but should validate attachment metadata before display
- Consider adding size limits and content type validation for attachments to prevent potential security issues

<sub>5 file(s) reviewed, 6 comment(s)</sub>
<sub>[Edit PR Review Bot Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews&utm_content=sim_412)</sub>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-25T04:52:31Z

Review comments:
diff_hunk: @@ -64,35 +65,70 @@ export const readChatTool: ToolConfig<MicrosoftTeamsToolParams, MicrosoftTeamsRe
             chatId: '',
             messageCount: 0,
             messages: [],
+            totalAttachments: 0,
+            attachmentTypes: [],
           },
         },
       }
     }
 
-    // Format the messages into a readable text
-    const formattedMessages = messages
+    if (!params?.chatId) {
+      throw new Error('Missing required parameter: chatId')
+    }
path: apps/sim/tools/microsoft_teams/read_chat.ts
user: login: greptile-apps[bot]
body: logic: This check is redundant since chatId is already validated in the request.url function
created_at: 2025-05-25T04:51:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -64,35 +65,70 @@ export const readChatTool: ToolConfig<MicrosoftTeamsToolParams, MicrosoftTeamsRe
             chatId: '',
             messageCount: 0,
             messages: [],
+            totalAttachments: 0,
+            attachmentTypes: [],
           },
         },
       }
     }
 
-    // Format the messages into a readable text
-    const formattedMessages = messages
+    if (!params?.chatId) {
+      throw new Error('Missing required parameter: chatId')
+    }
+
+    // Process messages with attachments
+    const processedMessages = messages.map((message: any) => {
+      const content = message.body?.content || 'No content'
path: apps/sim/tools/microsoft_teams/read_chat.ts
user: login: greptile-apps[bot]
body: style: Missing type safety for message object structure. Consider adding type definition for the Microsoft Teams API response
created_at: 2025-05-25T04:51:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -64,35 +65,70 @@ export const readChatTool: ToolConfig<MicrosoftTeamsToolParams, MicrosoftTeamsRe
             chatId: '',
             messageCount: 0,
             messages: [],
+            totalAttachments: 0,
+            attachmentTypes: [],
           },
         },
       }
     }
 
-    // Format the messages into a readable text
-    const formattedMessages = messages
+    if (!params?.chatId) {
+      throw new Error('Missing required parameter: chatId')
+    }
+
+    // Process messages with attachments
+    const processedMessages = messages.map((message: any) => {
+      const content = message.body?.content || 'No content'
+      const messageId = message.id
+
+      // Extract attachments without any content processing
+      const attachments = extractMessageAttachments(message)
+
+      return {
+        id: messageId,
+        content: content, // Keep original content without modification
+        sender: message.from?.user?.displayName || 'Unknown',
+        timestamp: message.createdDateTime,
+        messageType: message.messageType || 'message',
+        attachments, // Attachments only stored here
+      }
+    })
+
+    // Format the messages into a readable text (no attachment info in content)
+    const formattedMessages = processedMessages
       .map((message: any) => {
-        const content = message.body?.content || 'No content'
-        const sender = message.from?.user?.displayName || 'Unknown sender'
-        const timestamp = message.createdDateTime
-          ? new Date(message.createdDateTime).toLocaleString()
+        const sender = message.sender
+        const timestamp = message.timestamp
+          ? new Date(message.timestamp).toLocaleString()
           : 'Unknown time'
 
-        return `[${timestamp}] ${sender}: ${content}`
+        return `[${timestamp}] ${sender}: ${message.content}`
       })
       .join('\n\n')
 
+    // Calculate attachment statistics
+    const allAttachments = processedMessages.flatMap((msg: any) => msg.attachments || [])
+    const attachmentTypes: string[] = []
+    const seenTypes = new Set<string>()
+
+    allAttachments.forEach((att: any) => {
+      if (
+        att.contentType &&
+        typeof att.contentType === 'string' &&
+        !seenTypes.has(att.contentType)
+      ) {
+        attachmentTypes.push(att.contentType)
+        seenTypes.add(att.contentType)
+      }
+    })
+
     // Create document metadata
     const metadata = {
-      chatId: messages[0]?.chatId || '',
+      chatId: messages[0]?.chatId || params.chatId || '',
path: apps/sim/tools/microsoft_teams/read_chat.ts
user: login: greptile-apps[bot]
body: logic: messages[0]?.chatId is likely undefined as it's not a standard field in Teams API response. Remove the redundant check

```suggestion
      chatId: params.chatId || '',
```
created_at: 2025-05-25T04:51:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -64,35 +65,70 @@ export const readChatTool: ToolConfig<MicrosoftTeamsToolParams, MicrosoftTeamsRe
             chatId: '',
             messageCount: 0,
             messages: [],
+            totalAttachments: 0,
+            attachmentTypes: [],
           },
         },
       }
     }
 
-    // Format the messages into a readable text
-    const formattedMessages = messages
+    if (!params?.chatId) {
+      throw new Error('Missing required parameter: chatId')
+    }
+
+    // Process messages with attachments
+    const processedMessages = messages.map((message: any) => {
+      const content = message.body?.content || 'No content'
+      const messageId = message.id
+
+      // Extract attachments without any content processing
+      const attachments = extractMessageAttachments(message)
+
+      return {
+        id: messageId,
+        content: content, // Keep original content without modification
+        sender: message.from?.user?.displayName || 'Unknown',
+        timestamp: message.createdDateTime,
+        messageType: message.messageType || 'message',
+        attachments, // Attachments only stored here
+      }
+    })
+
+    // Format the messages into a readable text (no attachment info in content)
+    const formattedMessages = processedMessages
       .map((message: any) => {
-        const content = message.body?.content || 'No content'
-        const sender = message.from?.user?.displayName || 'Unknown sender'
-        const timestamp = message.createdDateTime
-          ? new Date(message.createdDateTime).toLocaleString()
+        const sender = message.sender
+        const timestamp = message.timestamp
+          ? new Date(message.timestamp).toLocaleString()
           : 'Unknown time'
path: apps/sim/tools/microsoft_teams/read_chat.ts
user: login: greptile-apps[bot]
body: logic: Inconsistent timestamp usage - using message.timestamp but original data uses createdDateTime

```suggestion
        const timestamp = message.createdDateTime
          ? new Date(message.createdDateTime).toLocaleString()
          : 'Unknown time'
```
created_at: 2025-05-25T04:51:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -81,36 +91,91 @@ export const readChannelTool: ToolConfig<MicrosoftTeamsToolParams, MicrosoftTeam
             channelId: '',
             messageCount: 0,
             messages: [],
+            totalAttachments: 0,
+            attachmentTypes: [],
           },
         },
       }
     }
 
-    // Format the messages into a readable text
-    const formattedMessages = messages
-      .map((message: any) => {
+    if (!params?.teamId || !params?.channelId) {
+      throw new Error('Missing required parameters: teamId and channelId')
+    }
+    // Process messages with attachments
+    const processedMessages = messages.map((message: any, index: number) => {
path: apps/sim/tools/microsoft_teams/read_channel.ts
user: login: greptile-apps[bot]
body: style: Type 'any' used for message parameter could lead to runtime errors if API response structure changes
created_at: 2025-05-25T04:51:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,13 +1,25 @@
 import type { ToolResponse } from '../types'
 
+export interface MicrosoftTeamsAttachment {
+  id: string
+  contentType: string
+  contentUrl?: string
+  content?: string
+  name?: string
+  thumbnailUrl?: string
+  size?: number
+  sourceUrl?: string
+  providerType?: string
+  item?: any
path: apps/sim/tools/microsoft_teams/types.ts
user: login: greptile-apps[bot]
body: style: item field type 'any' is too permissive and could lead to type safety issues. Consider defining a more specific type or interface
created_at: 2025-05-25T04:52:01Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-05-25T04:50:39Z
body: [vc]: #24kVvxEFUpI4yy+DZ9qoVPa5YkVcFygM//U5ofZlgFA=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJkb2NzIiwicm9vdERpcmVjdG9yeSI6ImFwcHMvZG9jcyIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiIifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3NpbS1zdHVkaW8vZG9jcy9FV3ZZdUVKOWRRb2VDR0hGODVDS1FnU00zclRUIiwicHJldmlld1VybCI6IiIsIm5leHRDb21taXRTdGF0dXMiOiJTS0lQUEVEIn0seyJuYW1lIjoic2ltIiwicm9vdERpcmVjdG9yeSI6ImFwcHMvc2ltIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3NpbS1zdHVkaW8vc2ltL0d6WnZ1YkRyY2FNdWNUam9CMVFLQXNHeDR1RGsiLCJwcmV2aWV3VXJsIjoic2ltLWdpdC1mb3JrLWFhZGFtZ291Z2gtZmVhdC10ZWFtcy1hdHRhY2htZW50cy1zaW0tc3R1ZGlvLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoic2ltLWdpdC1mb3JrLWFhZGFtZ291Z2gtZmVhdC10ZWFtcy1hdHRhY2htZW50cy1zaW0tc3R1ZGlvLnZlcmNlbC5hcHAifX1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **sim** | ✅ Ready ([Inspect](https://vercel.com/sim-studio/sim/GzZvubDrcaMucTjoB1QKAsGx4uDk)) | [Visit Preview](https://sim-git-fork-aadamgough-feat-teams-attachments-sim-studio.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/sim-git-fork-aadamgough-feat-teams-attachments-sim-studio.vercel.app?via=pr-comment-feedback-link) | May 25, 2025 5:01am |

<details><summary>1 Skipped Deployment</summary>

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **docs** | ⬜️ Skipped ([Inspect](https://vercel.com/sim-studio/docs/EWvYuEJ9dQoeCGHF85CKQgSM3rTT)) |  |  | May 25, 2025 5:01am |
</details>



author_association: NONE
user: login: vercel[bot]
created_at: 2025-05-25T04:50:40Z
body: Someone is attempting to deploy a commit to the **Sim Studio** Team on [Vercel](https://vercel.com).

A member of the Team first needs to [authorize it](https://vercel.com/git/authorize?team=Sim%20Studio&type=github&job=%7B%22headInfo%22%3A%7B%22sha%22%3A%22b2d91362390d71fdca31fe9b988f8011196130c7%22%7D%2C%22id%22%3A%22QmUYTiDyigvfQ5Lt9CovrYfY91f3HJJWiyWAer4aD1aq2B%22%2C%22org%22%3A%22simstudioai%22%2C%22prId%22%3A412%2C%22repo%22%3A%22sim%22%7D).


author_association: NONE