@clonecommand/cloud 0.0.7 → 0.0.10

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.
@@ -1,5 +1,5 @@
1
1
  import { CloneCommandCloud } from "..";
2
- import { InboundEmail, SendEmailOptions } from "./types";
2
+ import { InboundEmail, SendEmailOptions, EmailAttachment } from "./types";
3
3
  /**
4
4
  * Client for interacting with CloneCommand Email services.
5
5
  */
@@ -21,7 +21,8 @@ export declare class EmailClient {
21
21
  *
22
22
  * @example
23
23
  * await ccc.email.send({
24
- * to: 'user@example.com',
24
+ * from: { name: 'Support', address: 'support@mail.example.com' },
25
+ * to: [{ name: 'User', address: 'user@example.com' }],
25
26
  * subject: 'Hello',
26
27
  * text: 'This is a test'
27
28
  * });
@@ -58,4 +59,51 @@ export declare class EmailClient {
58
59
  * @returns A promise resolving to true if successful.
59
60
  */
60
61
  delete(emailId: string): Promise<boolean>;
62
+ /**
63
+ * Sends a raw MIME-formatted email.
64
+ * This is useful for sending emails with attachments, like calendar invites.
65
+ *
66
+ * @param rawMessage - The full MIME message as a string, including headers and body.
67
+ * @returns A promise resolving to the Message-ID of the sent email.
68
+ * @throws Error if sending fails.
69
+ *
70
+ * @example
71
+ * const boundary = `----=_Part_${Date.now()}`;
72
+ * const rawMessage = `From: events@mail.yourdomain.com
73
+ * To: user@example.com
74
+ * Subject: Meeting Invite
75
+ * MIME-Version: 1.0
76
+ * Content-Type: multipart/mixed; boundary="${boundary}"
77
+ *
78
+ * --${boundary}
79
+ * Content-Type: text/plain
80
+ *
81
+ * You're invited to a meeting.
82
+ *
83
+ * --${boundary}
84
+ * Content-Type: text/calendar; method=REQUEST; name="invite.ics"
85
+ * Content-Disposition: attachment; filename="invite.ics"
86
+ *
87
+ * BEGIN:VCALENDAR
88
+ * ...
89
+ * END:VCALENDAR
90
+ * --${boundary}--`;
91
+ *
92
+ * await ccc.email.sendRaw(rawMessage);
93
+ */
94
+ sendRaw(rawMessage: string): Promise<string>;
95
+ /**
96
+ * Retrieves the attachments from a specific inbound email.
97
+ *
98
+ * @param emailId - The unique ID of the email.
99
+ * @returns A promise resolving to an array of attachments.
100
+ *
101
+ * @example
102
+ * const attachments = await ccc.email.getAttachments(emailId);
103
+ * for (const att of attachments) {
104
+ * const content = Buffer.from(att.content, 'base64');
105
+ * console.log(`${att.filename}: ${att.contentType}, ${att.size} bytes`);
106
+ * }
107
+ */
108
+ getAttachments(emailId: string): Promise<EmailAttachment[]>;
61
109
  }
@@ -43,7 +43,8 @@ export class EmailClient {
43
43
  *
44
44
  * @example
45
45
  * await ccc.email.send({
46
- * to: 'user@example.com',
46
+ * from: { name: 'Support', address: 'support@mail.example.com' },
47
+ * to: [{ name: 'User', address: 'user@example.com' }],
47
48
  * subject: 'Hello',
48
49
  * text: 'This is a test'
49
50
  * });
@@ -57,6 +58,13 @@ export class EmailClient {
57
58
  sendEmail(projectId: $projectId, email: $email)
58
59
  }
59
60
  `;
61
+ const normalize = (to) => {
62
+ if (typeof to === 'string')
63
+ return { address: to };
64
+ return to;
65
+ };
66
+ const toList = Array.isArray(options.to) ? options.to : [options.to];
67
+ const normalizedTo = toList.map(normalize);
60
68
  const result = await this.ccc.request('/graphql', {
61
69
  method: 'POST',
62
70
  headers: { 'Content-Type': 'application/json' },
@@ -66,7 +74,7 @@ export class EmailClient {
66
74
  projectId: pid,
67
75
  email: {
68
76
  from: options.from,
69
- to: Array.isArray(options.to) ? options.to : [options.to],
77
+ to: normalizedTo,
70
78
  subject: options.subject,
71
79
  html: options.html,
72
80
  text: options.text
@@ -202,4 +210,95 @@ export class EmailClient {
202
210
  throw new Error(result.errors[0].message);
203
211
  return result.data.deleteEmail;
204
212
  }
213
+ /**
214
+ * Sends a raw MIME-formatted email.
215
+ * This is useful for sending emails with attachments, like calendar invites.
216
+ *
217
+ * @param rawMessage - The full MIME message as a string, including headers and body.
218
+ * @returns A promise resolving to the Message-ID of the sent email.
219
+ * @throws Error if sending fails.
220
+ *
221
+ * @example
222
+ * const boundary = `----=_Part_${Date.now()}`;
223
+ * const rawMessage = `From: events@mail.yourdomain.com
224
+ * To: user@example.com
225
+ * Subject: Meeting Invite
226
+ * MIME-Version: 1.0
227
+ * Content-Type: multipart/mixed; boundary="${boundary}"
228
+ *
229
+ * --${boundary}
230
+ * Content-Type: text/plain
231
+ *
232
+ * You're invited to a meeting.
233
+ *
234
+ * --${boundary}
235
+ * Content-Type: text/calendar; method=REQUEST; name="invite.ics"
236
+ * Content-Disposition: attachment; filename="invite.ics"
237
+ *
238
+ * BEGIN:VCALENDAR
239
+ * ...
240
+ * END:VCALENDAR
241
+ * --${boundary}--`;
242
+ *
243
+ * await ccc.email.sendRaw(rawMessage);
244
+ */
245
+ async sendRaw(rawMessage) {
246
+ const pid = this.ccc.projectId;
247
+ if (!pid)
248
+ throw new Error("projectId could not be resolved. Ensure CC_PROJECT_TOKEN is set.");
249
+ const query = `
250
+ mutation SendRawEmail($projectId: ID!, $rawMessage: String!) {
251
+ sendRawEmail(projectId: $projectId, rawMessage: $rawMessage)
252
+ }
253
+ `;
254
+ const result = await this.ccc.request('/graphql', {
255
+ method: 'POST',
256
+ headers: { 'Content-Type': 'application/json' },
257
+ body: JSON.stringify({
258
+ query,
259
+ variables: { projectId: pid, rawMessage }
260
+ })
261
+ });
262
+ if (result.errors)
263
+ throw new Error(result.errors[0].message);
264
+ return result.data.sendRawEmail;
265
+ }
266
+ /**
267
+ * Retrieves the attachments from a specific inbound email.
268
+ *
269
+ * @param emailId - The unique ID of the email.
270
+ * @returns A promise resolving to an array of attachments.
271
+ *
272
+ * @example
273
+ * const attachments = await ccc.email.getAttachments(emailId);
274
+ * for (const att of attachments) {
275
+ * const content = Buffer.from(att.content, 'base64');
276
+ * console.log(`${att.filename}: ${att.contentType}, ${att.size} bytes`);
277
+ * }
278
+ */
279
+ async getAttachments(emailId) {
280
+ const query = `
281
+ query Email($id: ID!) {
282
+ email(id: $id) {
283
+ attachments {
284
+ filename
285
+ contentType
286
+ size
287
+ content
288
+ }
289
+ }
290
+ }
291
+ `;
292
+ const result = await this.ccc.request('/graphql', {
293
+ method: 'POST',
294
+ headers: { 'Content-Type': 'application/json' },
295
+ body: JSON.stringify({
296
+ query,
297
+ variables: { id: emailId }
298
+ })
299
+ });
300
+ if (result.errors)
301
+ throw new Error(result.errors[0].message);
302
+ return result.data.email?.attachments || [];
303
+ }
205
304
  }
@@ -35,17 +35,25 @@ export interface InboundEmail {
35
35
  /** References header. */
36
36
  references?: string;
37
37
  }
38
+ /**
39
+ * Represents an email address with an optional display name.
40
+ */
41
+ export interface EmailAddress {
42
+ /** The display name of the account (e.g., "John Doe"). */
43
+ name?: string;
44
+ /** The raw email address (e.g., "john@example.com"). */
45
+ address: string;
46
+ }
38
47
  /**
39
48
  * Options for sending an email.
40
49
  */
41
50
  export interface SendEmailOptions {
42
51
  /**
43
52
  * The sender address. Must be a verified domain.
44
- * Use "Name <email@domain.com>" or just "email@domain.com".
45
53
  */
46
- from?: string;
47
- /** The recipient(s). Can be a string or an array of strings. */
48
- to: string | string[];
54
+ from?: EmailAddress;
55
+ /** The recipient(s). Can be a string, an EmailAddress object, or an array of either. */
56
+ to: string | EmailAddress | (string | EmailAddress)[];
49
57
  /** The subject line. */
50
58
  subject: string;
51
59
  /** The HTML body of the email. */
@@ -53,3 +61,16 @@ export interface SendEmailOptions {
53
61
  /** The plain text body of the email. */
54
62
  text?: string;
55
63
  }
64
+ /**
65
+ * Represents an email attachment.
66
+ */
67
+ export interface EmailAttachment {
68
+ /** The filename of the attachment. */
69
+ filename: string;
70
+ /** The MIME type of the attachment. */
71
+ contentType: string;
72
+ /** The size of the attachment in bytes. */
73
+ size: number;
74
+ /** The content of the attachment, Base64 encoded. */
75
+ content: string;
76
+ }
package/docs/email.md CHANGED
@@ -5,7 +5,9 @@ Managed email sending and receiving with inbound processing and domain verificat
5
5
  ## Features
6
6
 
7
7
  - **Send Emails**: Send transactional emails via Amazon SES (managed backend).
8
+ - **Send Raw Emails**: Send MIME-formatted emails with attachments (e.g., calendar invites).
8
9
  - **Inbound Processing**: Receive emails, store them in GCS, and access them via API.
10
+ - **Attachment Support**: Extract and download attachments from inbound emails.
9
11
  - **Domain Verification**: Automates DNS verification for custom domains.
10
12
 
11
13
  ## Usage
@@ -37,6 +39,43 @@ const messageId = await ccc.email.send({
37
39
  });
38
40
  ```
39
41
 
42
+ ### Sending Raw Emails (with Attachments)
43
+
44
+ Send MIME-formatted emails for advanced use cases like calendar invites.
45
+
46
+ ```typescript
47
+ // Construct a MIME message with an attachment
48
+ const boundary = `----=_Part_${Date.now()}`;
49
+ const rawMessage = `From: events@mail.yourdomain.com
50
+ To: user@example.com
51
+ Subject: Meeting Invite
52
+ MIME-Version: 1.0
53
+ Content-Type: multipart/mixed; boundary="${boundary}"
54
+
55
+ --${boundary}
56
+ Content-Type: text/plain; charset="utf-8"
57
+
58
+ You have been invited to a meeting.
59
+
60
+ --${boundary}
61
+ Content-Type: text/calendar; method=REQUEST; name="invite.ics"
62
+ Content-Disposition: attachment; filename="invite.ics"
63
+
64
+ BEGIN:VCALENDAR
65
+ VERSION:2.0
66
+ METHOD:REQUEST
67
+ BEGIN:VEVENT
68
+ UID:event-123@yourapp.com
69
+ DTSTART:20260210T100000Z
70
+ DTEND:20260210T110000Z
71
+ SUMMARY:Team Meeting
72
+ END:VEVENT
73
+ END:VCALENDAR
74
+ --${boundary}--`;
75
+
76
+ const messageId = await ccc.email.sendRaw(rawMessage);
77
+ ```
78
+
40
79
  ### Retrieving Inbound Emails
41
80
 
42
81
  Fetch the latest emails received by the project's inbound address.
@@ -54,12 +93,35 @@ for (const email of emails) {
54
93
  }
55
94
  ```
56
95
 
96
+ ### Retrieving Attachments
97
+
98
+ Get attachments from an inbound email.
99
+
100
+ ```typescript
101
+ const attachments = await ccc.email.getAttachments(emailId);
102
+
103
+ for (const att of attachments) {
104
+ console.log(`${att.filename}: ${att.contentType}, ${att.size} bytes`);
105
+
106
+ // Decode the Base64 content
107
+ const content = Buffer.from(att.content, 'base64');
108
+
109
+ // For calendar responses, parse the ICS
110
+ if (att.contentType.includes('calendar')) {
111
+ const icsText = content.toString('utf-8');
112
+ console.log('Calendar data:', icsText);
113
+ }
114
+ }
115
+ ```
116
+
57
117
  ## Agent Capabilities
58
118
 
59
119
  The email service is designed to be easily used by AI agents to build automated workflows:
60
120
  - **Auto-Reply Bots**: Poll `getInbound()` and `send()` replies.
61
121
  - **Verification flows**: Trigger emails and read the verification codes from the inbox.
122
+ - **Calendar Workflows**: Send calendar invites with `sendRaw()` and process responses via `getAttachments()`.
62
123
 
63
124
  ## Authentication
64
125
 
65
126
  Requires the `CC_PROJECT_TOKEN` environment variable, automatically injected in CloneCommand deployments.
127
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clonecommand/cloud",
3
- "version": "0.0.7",
3
+ "version": "0.0.10",
4
4
  "type": "module",
5
5
  "description": "The official SDK for CloneCommand managed services",
6
6
  "main": "dist/index.js",
@@ -33,4 +33,4 @@
33
33
  "devDependencies": {
34
34
  "typescript": "^5.0.0"
35
35
  }
36
- }
36
+ }