@clonecommand/cloud 0.0.4 → 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 { EmailConfig, InboundEmail, SendEmailOptions } from "./types";
2
+ import { InboundEmail, SendEmailOptions, EmailAttachment } from "./types";
3
3
  /**
4
4
  * Client for interacting with CloneCommand Email services.
5
5
  */
@@ -7,20 +7,11 @@ export declare class EmailClient {
7
7
  private ccc;
8
8
  constructor(ccc: CloneCommandCloud);
9
9
  /**
10
- * Retrieves the email configuration for the current project.
10
+ * Checks if the email service is enabled for the current project.
11
11
  *
12
- * @returns A promise resolving to the EmailConfig or null if not found.
13
- * @throws Error if projectId is missing or the API returns an error.
12
+ * @returns A promise resolving to true if enabled, false otherwise.
14
13
  */
15
- getConfig(): Promise<EmailConfig | null>;
16
- /**
17
- * Enables the email service for a specific domain.
18
- *
19
- * @param domainId - The ID of the domain to enable email for.
20
- * @returns A promise resolving to the updated EmailConfig.
21
- * @throws Error if activation fails.
22
- */
23
- enable(domainId: string): Promise<EmailConfig>;
14
+ isEnabled(): Promise<boolean>;
24
15
  /**
25
16
  * Sends an email message.
26
17
  *
@@ -30,7 +21,8 @@ export declare class EmailClient {
30
21
  *
31
22
  * @example
32
23
  * await ccc.email.send({
33
- * to: 'user@example.com',
24
+ * from: { name: 'Support', address: 'support@mail.example.com' },
25
+ * to: [{ name: 'User', address: 'user@example.com' }],
34
26
  * subject: 'Hello',
35
27
  * text: 'This is a test'
36
28
  * });
@@ -68,9 +60,50 @@ export declare class EmailClient {
68
60
  */
69
61
  delete(emailId: string): Promise<boolean>;
70
62
  /**
71
- * Refreshes the email verification status for the current project.
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.
72
100
  *
73
- * @returns A promise resolving to the status string.
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
+ * }
74
107
  */
75
- refreshEmailVerificationStatus(): Promise<string>;
108
+ getAttachments(emailId: string): Promise<EmailAttachment[]>;
76
109
  }
@@ -7,24 +7,18 @@ export class EmailClient {
7
7
  this.ccc = ccc;
8
8
  }
9
9
  /**
10
- * Retrieves the email configuration for the current project.
10
+ * Checks if the email service is enabled for the current project.
11
11
  *
12
- * @returns A promise resolving to the EmailConfig or null if not found.
13
- * @throws Error if projectId is missing or the API returns an error.
12
+ * @returns A promise resolving to true if enabled, false otherwise.
14
13
  */
15
- async getConfig() {
14
+ async isEnabled() {
16
15
  const pid = this.ccc.projectId;
17
16
  if (!pid)
18
17
  throw new Error("projectId could not be resolved. Ensure CC_PROJECT_TOKEN is set.");
19
18
  const query = `
20
- query EmailConfig($projectId: ID!) {
19
+ query EmailStatus($projectId: ID!) {
21
20
  emailConfig(projectId: $projectId) {
22
- id
23
- projectId
24
- domainId
25
21
  isEnabled
26
- sesVerificationStatus
27
- dkimTokens
28
22
  }
29
23
  }
30
24
  `;
@@ -38,42 +32,7 @@ export class EmailClient {
38
32
  });
39
33
  if (result.errors)
40
34
  throw new Error(result.errors[0].message);
41
- return result.data.emailConfig;
42
- }
43
- /**
44
- * Enables the email service for a specific domain.
45
- *
46
- * @param domainId - The ID of the domain to enable email for.
47
- * @returns A promise resolving to the updated EmailConfig.
48
- * @throws Error if activation fails.
49
- */
50
- async enable(domainId) {
51
- const pid = this.ccc.projectId;
52
- if (!pid)
53
- throw new Error("projectId could not be resolved. Ensure CC_PROJECT_TOKEN is set.");
54
- const query = `
55
- mutation EnableEmail($projectId: ID!, $domainId: ID!) {
56
- enableEmail(projectId: $projectId, domainId: $domainId) {
57
- id
58
- projectId
59
- domainId
60
- isEnabled
61
- sesVerificationStatus
62
- dkimTokens
63
- }
64
- }
65
- `;
66
- const result = await this.ccc.request('/graphql', {
67
- method: 'POST',
68
- headers: { 'Content-Type': 'application/json' },
69
- body: JSON.stringify({
70
- query,
71
- variables: { projectId: pid, domainId }
72
- })
73
- });
74
- if (result.errors)
75
- throw new Error(result.errors[0].message);
76
- return result.data.enableEmail;
35
+ return !!result.data.emailConfig?.isEnabled;
77
36
  }
78
37
  /**
79
38
  * Sends an email message.
@@ -84,7 +43,8 @@ export class EmailClient {
84
43
  *
85
44
  * @example
86
45
  * await ccc.email.send({
87
- * to: 'user@example.com',
46
+ * from: { name: 'Support', address: 'support@mail.example.com' },
47
+ * to: [{ name: 'User', address: 'user@example.com' }],
88
48
  * subject: 'Hello',
89
49
  * text: 'This is a test'
90
50
  * });
@@ -98,6 +58,13 @@ export class EmailClient {
98
58
  sendEmail(projectId: $projectId, email: $email)
99
59
  }
100
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);
101
68
  const result = await this.ccc.request('/graphql', {
102
69
  method: 'POST',
103
70
  headers: { 'Content-Type': 'application/json' },
@@ -107,7 +74,7 @@ export class EmailClient {
107
74
  projectId: pid,
108
75
  email: {
109
76
  from: options.from,
110
- to: Array.isArray(options.to) ? options.to : [options.to],
77
+ to: normalizedTo,
111
78
  subject: options.subject,
112
79
  html: options.html,
113
80
  text: options.text
@@ -244,24 +211,94 @@ export class EmailClient {
244
211
  return result.data.deleteEmail;
245
212
  }
246
213
  /**
247
- * Refreshes the email verification status for the current project.
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"
248
237
  *
249
- * @returns A promise resolving to the status string.
238
+ * BEGIN:VCALENDAR
239
+ * ...
240
+ * END:VCALENDAR
241
+ * --${boundary}--`;
242
+ *
243
+ * await ccc.email.sendRaw(rawMessage);
250
244
  */
251
- async refreshEmailVerificationStatus() {
245
+ async sendRaw(rawMessage) {
252
246
  const pid = this.ccc.projectId;
253
247
  if (!pid)
254
248
  throw new Error("projectId could not be resolved. Ensure CC_PROJECT_TOKEN is set.");
255
- const status = await this.ccc.request('/graphql', {
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', {
256
255
  method: 'POST',
257
256
  headers: { 'Content-Type': 'application/json' },
258
257
  body: JSON.stringify({
259
- query: `mutation RefreshVerification($projectId: ID!) { refreshEmailVerificationStatus(projectId: $projectId) }`,
260
- variables: { projectId: pid }
258
+ query,
259
+ variables: { projectId: pid, rawMessage }
261
260
  })
262
261
  });
263
- if (status.errors)
264
- throw new Error(status.errors[0].message);
265
- return status.data.refreshEmailVerificationStatus;
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 || [];
266
303
  }
267
304
  }
@@ -1,22 +1,3 @@
1
- /**
2
- * Configuration for the email service.
3
- */
4
- export interface EmailConfig {
5
- /** Unique ID for the email configuration. */
6
- id: string;
7
- /** Project ID this configuration belongs to. */
8
- projectId: string;
9
- /** The domain ID associated with this email service. */
10
- domainId: string;
11
- /** Whether the email service is currently enabled. */
12
- isEnabled: boolean;
13
- /** The Amazon SES Identity ARN. */
14
- sesIdentityArn?: string;
15
- /** Current verification status in SES. */
16
- sesVerificationStatus?: string;
17
- /** DKIM verification tokens as a JSON string. */
18
- dkimTokens?: string;
19
- }
20
1
  /**
21
2
  * Represents an inbound email received by the system.
22
3
  */
@@ -54,17 +35,25 @@ export interface InboundEmail {
54
35
  /** References header. */
55
36
  references?: string;
56
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
+ }
57
47
  /**
58
48
  * Options for sending an email.
59
49
  */
60
50
  export interface SendEmailOptions {
61
51
  /**
62
52
  * The sender address. Must be a verified domain.
63
- * Use "Name <email@domain.com>" or just "email@domain.com".
64
53
  */
65
- from?: string;
66
- /** The recipient(s). Can be a string or an array of strings. */
67
- 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)[];
68
57
  /** The subject line. */
69
58
  subject: string;
70
59
  /** The HTML body of the email. */
@@ -72,3 +61,16 @@ export interface SendEmailOptions {
72
61
  /** The plain text body of the email. */
73
62
  text?: string;
74
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,22 +5,24 @@ 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
12
14
 
13
- ### Getting Configuration
15
+ ### Checking Status
14
16
 
15
- Check if email is enabled for the current project.
17
+ Check if the email service is enabled and ready to use for the current project.
16
18
 
17
19
  ```typescript
18
20
  import { ccc } from '@clonecommand/cloud';
19
21
 
20
- const config = await ccc.email.getConfig();
22
+ const enabled = await ccc.email.isEnabled();
21
23
 
22
- if (config?.isEnabled) {
23
- console.log('Email is active!');
24
+ if (enabled) {
25
+ console.log('Email is active and ready to send!');
24
26
  }
25
27
  ```
26
28
 
@@ -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.4",
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
+ }