commune-ai 0.2.5 → 0.2.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -132,6 +132,53 @@ const client = new CommuneClient({ apiKey: process.env.COMMUNE_API_KEY! });
132
132
 
133
133
  ---
134
134
 
135
+ ## Attachments
136
+ Access email attachments with secure, temporary download URLs.
137
+
138
+ Attachments are stored and available as `attachment_id`. These IDs are:
139
+ - Available through email events in the incoming webhook
140
+ - Available in metadata of semantic search results
141
+
142
+ Use the `attachment_id` to directly access the attachment, download it or read it.
143
+
144
+ ```ts
145
+ // Get attachment metadata
146
+ const attachment = await client.attachments.get("att_123");
147
+ console.log(attachment.filename, attachment.size);
148
+
149
+ // Get download URL (expires in 1 hour)
150
+ const { url } = await client.attachments.get("att_123", { url: true });
151
+ // Use the URL to download or display the file
152
+
153
+ // Custom expiration (2 hours)
154
+ const { url } = await client.attachments.get("att_123", { url: true, expiresIn: 7200 });
155
+ ```
156
+
157
+ ### Handling attachments in webhook
158
+ ```ts
159
+ const handler = createWebhookHandler({
160
+ onEvent: async (message, context) => {
161
+ const { attachments } = context.payload;
162
+
163
+ if (attachments && attachments.length > 0) {
164
+ for (const att of attachments) {
165
+ console.log(`Attachment: ${att.filename} (${att.size} bytes)`);
166
+
167
+ // Get download URL
168
+ const { url } = await client.attachments.get(att.attachment_id, { url: true });
169
+
170
+ // Download the file
171
+ const response = await fetch(url);
172
+ const buffer = await response.arrayBuffer();
173
+ // Process the file...
174
+ }
175
+ }
176
+ },
177
+ });
178
+ ```
179
+
180
+ ---
181
+
135
182
  ## Semantic Search
136
183
  Commune provides powerful semantic search capabilities to help your agent find relevant conversations and context. The search is powered by embeddings and vector similarity, allowing natural language queries.
137
184
 
@@ -230,10 +277,75 @@ interface SearchResult {
230
277
  participants: string[];
231
278
  threadId: string;
232
279
  timestamp: Date;
280
+ attachmentIds?: string[]; // Attachment IDs in this conversation
281
+ hasAttachments?: boolean; // Whether conversation has attachments
282
+ attachmentCount?: number; // Number of attachments
233
283
  };
234
284
  }
235
285
  ```
236
286
 
287
+ ### Search with attachments
288
+ ```ts
289
+ // Search for conversations with attachments
290
+ const results = await client.searchConversations(
291
+ "invoice with receipt",
292
+ { organizationId: "org_123" }
293
+ );
294
+
295
+ // Filter results that have attachments
296
+ const withAttachments = results.filter(r => r.metadata.hasAttachments);
297
+
298
+ // Access attachments from search results
299
+ for (const result of withAttachments) {
300
+ for (const attachmentId of result.metadata.attachmentIds || []) {
301
+ const { url, filename } = await client.attachments.get(attachmentId, { url: true });
302
+ console.log(`Download: ${filename} - ${url}`);
303
+ }
304
+ }
305
+ ```
306
+
307
+ ## Spam Protection
308
+ Commune includes built-in spam detection and protection to keep your agent safe from malicious emails and prevent abuse.
309
+
310
+ ### Automatic Spam Detection
311
+ All incoming emails are automatically analyzed for spam patterns:
312
+ - Content analysis (spam keywords, suspicious patterns)
313
+ - URL validation (broken links, phishing detection)
314
+ - Sender reputation tracking
315
+ - Domain blacklist checking
316
+
317
+ Spam emails are automatically rejected before reaching your webhook, protecting your agent from:
318
+ - Phishing attempts
319
+ - Malicious links
320
+ - Mass spam campaigns
321
+ - Fraudulent content
322
+
323
+ ### Outbound Protection
324
+ Commune also protects your sending reputation:
325
+ - Rate limiting per organization tier
326
+ - Content validation for outbound emails
327
+ - Burst detection for mass mailing
328
+ - Automatic blocking of spam-like content
329
+
330
+ This ensures your domain maintains high deliverability and isn't flagged by email providers.
331
+
332
+ ### Spam Reporting
333
+ If a spam email gets through, you can report it:
334
+
335
+ ```ts
336
+ import { CommuneClient } from "@commune/sdk";
337
+ const client = new CommuneClient({ apiKey: process.env.COMMUNE_API_KEY! });
338
+
339
+ // Report a message as spam
340
+ await client.reportSpam({
341
+ message_id: "msg_123",
342
+ reason: "Unsolicited marketing email",
343
+ classification: "spam" // or "phishing", "malware", "other"
344
+ });
345
+ ```
346
+
347
+ The system learns from reports and automatically blocks repeat offenders.
348
+
237
349
  ## Context (conversation state)
238
350
  Commune stores conversation state so your agent can respond with context.
239
351
 
@@ -485,6 +597,7 @@ const handler = createWebhookHandler({
485
597
  ## Full example (single file)
486
598
  A complete copy‑paste example that:
487
599
  - receives webhook
600
+ - handles attachments
488
601
  - replies by email
489
602
  - replies in Slack thread
490
603
  - fetches conversation history
@@ -497,7 +610,23 @@ const client = new CommuneClient({ apiKey: process.env.COMMUNE_API_KEY! });
497
610
 
498
611
  const handler = createWebhookHandler({
499
612
  onEvent: async (message, context) => {
500
- // 1) Email reply (same thread)
613
+ // 1) Handle attachments
614
+ const { attachments } = context.payload;
615
+ if (attachments && attachments.length > 0) {
616
+ console.log(`Received ${attachments.length} attachments`);
617
+
618
+ for (const att of attachments) {
619
+ // Get download URL
620
+ const { url, filename } = await client.attachments.get(att.attachment_id, { url: true });
621
+ console.log(`Attachment: ${filename} - ${url}`);
622
+
623
+ // Download if needed
624
+ // const response = await fetch(url);
625
+ // const buffer = await response.arrayBuffer();
626
+ }
627
+ }
628
+
629
+ // 2) Email reply (same thread)
501
630
  if (message.channel === "email") {
502
631
  const sender = message.participants.find(p => p.role === "sender")?.identity;
503
632
  if (!sender) return;
@@ -514,7 +643,7 @@ const handler = createWebhookHandler({
514
643
  return;
515
644
  }
516
645
 
517
- // 2) Slack reply (same thread)
646
+ // 3) Slack reply (same thread)
518
647
  if (message.channel === "slack") {
519
648
  const channelId = message.metadata.slack_channel_id;
520
649
  if (!channelId) return;
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AttachmentRecord, CreateDomainPayload, DomainEntry, InboxEntry, MessageListParams, SendMessagePayload, UnifiedMessage, SearchFilter, SearchOptions, SearchResult, IndexConversationPayload } from './types.js';
1
+ import type { AttachmentRecord, AttachmentUrl, CreateDomainPayload, DomainEntry, InboxEntry, MessageListParams, SendMessagePayload, UnifiedMessage, SearchFilter, SearchOptions, SearchResult, IndexConversationPayload } from './types.js';
2
2
  export type ClientOptions = {
3
3
  baseUrl?: string;
4
4
  apiKey: string;
@@ -72,6 +72,9 @@ export declare class CommuneClient {
72
72
  }>;
73
73
  };
74
74
  attachments: {
75
- get: (attachmentId: string) => Promise<AttachmentRecord>;
75
+ get: (attachmentId: string, options?: {
76
+ url?: boolean;
77
+ expiresIn?: number;
78
+ }) => Promise<AttachmentRecord | AttachmentUrl>;
76
79
  };
77
80
  }
package/dist/client.js CHANGED
@@ -128,7 +128,11 @@ export class CommuneClient {
128
128
  },
129
129
  };
130
130
  this.attachments = {
131
- get: async (attachmentId) => {
131
+ get: async (attachmentId, options) => {
132
+ if (options?.url) {
133
+ const expiresIn = options.expiresIn || 3600;
134
+ return this.request(`/api/attachments/${encodeURIComponent(attachmentId)}/url?expiresIn=${expiresIn}`);
135
+ }
132
136
  return this.request(`/api/attachments/${encodeURIComponent(attachmentId)}`);
133
137
  },
134
138
  };
package/dist/types.d.ts CHANGED
@@ -42,6 +42,22 @@ export interface AttachmentRecord {
42
42
  source: Channel;
43
43
  source_url?: string | null;
44
44
  download_error?: boolean;
45
+ storage_type?: 'cloudinary' | 'database';
46
+ cloudinary_url?: string | null;
47
+ cloudinary_public_id?: string | null;
48
+ }
49
+ export interface AttachmentMetadata {
50
+ attachment_id: string;
51
+ filename: string;
52
+ mime_type: string;
53
+ size: number;
54
+ }
55
+ export interface AttachmentUrl {
56
+ url: string;
57
+ expiresIn: number;
58
+ filename: string;
59
+ mimeType: string;
60
+ size: number;
45
61
  }
46
62
  export interface DomainWebhook {
47
63
  id?: string;
@@ -142,6 +158,7 @@ export interface InboundEmailWebhookPayload {
142
158
  email: unknown;
143
159
  message: UnifiedMessage;
144
160
  extractedData?: Record<string, any>;
161
+ attachments?: AttachmentMetadata[];
145
162
  }
146
163
  export interface ApiError {
147
164
  message?: string;
@@ -175,6 +192,9 @@ export interface SearchResult {
175
192
  participants: string[];
176
193
  threadId: string;
177
194
  timestamp: Date;
195
+ attachmentIds?: string[];
196
+ hasAttachments?: boolean;
197
+ attachmentCount?: number;
178
198
  };
179
199
  }
180
200
  export interface ConversationMetadata {
@@ -185,6 +205,9 @@ export interface ConversationMetadata {
185
205
  participants: string[];
186
206
  threadId: string;
187
207
  timestamp: Date;
208
+ attachmentIds?: string[];
209
+ hasAttachments?: boolean;
210
+ attachmentCount?: number;
188
211
  }
189
212
  export interface IndexConversationPayload {
190
213
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commune-ai",
3
- "version": "0.2.5",
3
+ "version": "0.2.61",
4
4
  "description": "Our email infrastructure - webhooks, threads, history, and semantic search",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",