diff --git a/package.json b/package.json index e98619c3..af86e02f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@internxt/sdk", "author": "Internxt ", - "version": "1.17.1", + "version": "1.17.2", "description": "An sdk for interacting with Internxt's services", "repository": { "type": "git", @@ -26,7 +26,7 @@ "lint": "eslint ./src", "format": "prettier --write **/*.{js,jsx,tsx,ts}", "swagger": "openapi-typescript && yarn format", - "swagger:mail": "openapi-typescript http://localhost:3100/api-json -o ./src/mail/schema.ts && yarn format" + "swagger:mail": "openapi-typescript --redocly redocly.mail.yaml && yarn format" }, "devDependencies": { "@internxt/eslint-config-internxt": "^2.1.0", diff --git a/redocly.mail.yaml b/redocly.mail.yaml new file mode 100644 index 00000000..015d605a --- /dev/null +++ b/redocly.mail.yaml @@ -0,0 +1,5 @@ +apis: + mail: + root: http://localhost:3100/docs-json + x-openapi-ts: + output: ./src/mail/schema.ts diff --git a/src/mail/index.ts b/src/mail/index.ts index 3e539891..3f953b11 100644 --- a/src/mail/index.ts +++ b/src/mail/index.ts @@ -15,6 +15,7 @@ import { SearchFiltersQuery, MailAccountKeysResponse, MailAccountResponse, + LookupRecipientKeysResponse, EncryptedKeystore, KeystoreType, RecipientWithPublicKey, @@ -155,6 +156,18 @@ export class MailApi { return this.client.post('/email/send', body, this.headers()); } + /** + * Looks up the public encryption keys for one or more recipient addresses. + * For each address, returns the public key if it belongs to an active + * Internxt domain, or `null` for external or unknown addresses. + * + * @param addresses - 1-50 email addresses to look up + * @returns Recipients with their public keys (or null) + */ + lookupRecipientKeys(addresses: string[]): Promise { + return this.client.post('/email/keys/lookup', { addresses }, this.headers()); + } + /** * Saves a draft email * diff --git a/src/mail/schema.ts b/src/mail/schema.ts index 616661ce..f6ac077e 100644 --- a/src/mail/schema.ts +++ b/src/mail/schema.ts @@ -128,6 +128,26 @@ export interface paths { patch: operations['EmailController_update']; trace?: never; }; + '/email/keys/lookup': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Look up recipient public keys + * @description Returns the public encryption key for each address if it belongs to an active Internxt domain. Returns null for external or unknown addresses. + */ + post: operations['EmailController_lookupRecipientKeys']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; '/email/send': { parameters: { query?: never; @@ -168,32 +188,73 @@ export interface paths { patch?: never; trace?: never; }; - '/gateway/accounts': { + '/users/me/mail-account': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** + * Get the caller`s mail account status + * @description Returns the account status. When suspended, includes `suspendedAt` and the scheduled `deletionAt`. + */ + get: operations['UserController_getMailAccount']; + put?: never; + /** Provision the caller`s mail account */ + post: operations['UserController_createMailAccount']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/users/me/mail-account/keys': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get encryption keys and salt for one of the caller`s addresses + * @description If `address` is omitted, returns keys for the caller`s primary address. + */ + get: operations['UserController_getMailAccountKeys']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/addresses/availability': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Check address availability (called by the auth service) */ + get: operations['AddressesController_checkAvailability']; put?: never; - /** Provision a new mail account (called by the auth service) */ - post: operations['GatewayController_provisionAccount']; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - '/gateway/domains': { + '/gateway/addresses/{address}': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** List available mail domains (called by the auth service) */ - get: operations['GatewayController_listDomains']; + /** Get a mail address resource (used to resolve drive user id) */ + get: operations['GatewayController_getAddress']; put?: never; post?: never; delete?: never; @@ -275,6 +336,18 @@ export interface components { /** @example alice@internxt.me */ email: string; }; + EncryptedWrappedKeyDto: { + /** @description Hybrid ciphertext (base64) */ + hybridCiphertext: string; + /** @description Encrypted symmetric key (base64) */ + encryptedKey: string; + }; + EncryptedSummaryDto: { + /** @description Encrypted preview snippet (base64) */ + encryptedPreview: string; + /** @description De-identified wrapped keys; the client trial-decrypts to read */ + wrappedKeys: components['schemas']['EncryptedWrappedKeyDto'][]; + }; EmailSummaryResponseDto: { /** @example Ma1f09b… */ id: string; @@ -306,6 +379,8 @@ export interface components { * @example 4096 */ size: number; + /** @description Present only for encrypted emails. Carries the encrypted preview and the de-identified wrapped keys for inline client-side decryption. */ + encryption?: components['schemas']['EncryptedSummaryDto'] | null; }; EmailListResponseDto: { emails: components['schemas']['EmailSummaryResponseDto'][]; @@ -385,6 +460,8 @@ export interface components { * @example 4096 */ size: number; + /** @description Present only for encrypted emails. Carries the encrypted preview and the de-identified wrapped keys for inline client-side decryption. */ + encryption?: components['schemas']['EncryptedSummaryDto'] | null; cc: components['schemas']['EmailAddressDto'][]; bcc: components['schemas']['EmailAddressDto'][]; replyTo: components['schemas']['EmailAddressDto'][]; @@ -395,6 +472,35 @@ export interface components { /** @example

Hi team, here are the notes…

*/ htmlBody: string | null; }; + LookupRecipientKeysRequestDto: { + /** + * @description 1-50 email addresses to look up + * @example [ + * "alice@internxt.me", + * "bob@internxt.com" + * ] + */ + addresses: string[]; + }; + RecipientKeyDto: { + /** @example alice@internxt.me */ + address: string; + /** @example base64encodedpublickey== */ + publicKey: string | null; + }; + LookupRecipientKeysResponseDto: { + recipients: components['schemas']['RecipientKeyDto'][]; + }; + EncryptionBlockDto: { + /** @example v1 */ + version: string; + /** @description Encrypted preview snippet (base64), ~256 chars plaintext */ + encryptedPreview: string; + /** @description Encrypted text body (base64) */ + encryptedText: string; + /** @description De-identified wrapped keys, one per recipient */ + wrappedKeys: components['schemas']['EncryptedWrappedKeyDto'][]; + }; SendEmailRequestDto: { /** @description Primary recipients (at least one required) */ to: components['schemas']['EmailAddressDto'][]; @@ -412,6 +518,7 @@ export interface components { * @example

Hi team, here are the notes from today…

*/ htmlBody?: string; + encryption?: components['schemas']['EncryptionBlockDto']; }; EmailCreatedResponseDto: { /** @@ -449,27 +556,63 @@ export interface components { */ isFlagged?: boolean; }; - ProvisionAccountRequestDto: { + /** @enum {string} */ + MailAccountState: 'active' | 'suspended'; + MailAccountStatusResponseDto: { + /** @example f3a1b2c4-1234-4abc-9def-0123456789ab */ + id: string; /** - * @description User id - * @example d7ffe6b1-434d-4eae-86a5-029f76d1aa80 + * @description Default address of the account, null if none is set + * @example alice@inxt.eu */ - userId: string; + defaultAddress?: string | null; + /** @example active */ + status: components['schemas']['MailAccountState']; /** - * @description Full email address - * @example alice@internxt.com + * @description When the account was suspended; null when active + * @example 2026-05-01T10:29:55.000Z */ - address: string; + suspendedAt?: string | null; /** - * @description Email domain - * @example internxt.com + * @description Scheduled deletion date for suspended accounts; null when active + * @example 2026-05-31T10:29:55.000Z */ + deletionAt?: string | null; + }; + MailAddressKeyBundleDto: { + /** @description Hybrid (X25519 + ML-KEM-768) public key, base64-encoded */ + publicKey: string; + /** @description Private key encrypted with the encryption keystore key (base64) */ + encryptionPrivateKey: string; + /** @description Private key encrypted with the recovery keystore key (base64) */ + recoveryPrivateKey: string; + }; + CreateMailAccountDto: { + /** @example alice */ + address: string; + /** @example inxt.eu */ domain: string; - /** - * @description User display name - * @example Alice Smith - */ + /** @example Alice Smith */ displayName: string; + keys: components['schemas']['MailAddressKeyBundleDto']; + }; + CreateMailAccountResponseDto: { + /** @example f3a1b2c4-1234-4abc-9def-0123456789ab */ + id: string; + /** @example alice@inxt.eu */ + address: string; + /** @example inxt.eu */ + domain: string; + }; + MailAccountKeysResponseDto: { + /** @example alice@inxt.eu */ + address: string; + /** @description Hybrid (X25519 + ML-KEM-768) public key, base64-encoded */ + publicKey: string; + /** @description Private key encrypted with the encryption keystore key (base64) */ + encryptionPrivateKey: string; + /** @description Private key encrypted with the recovery keystore key (base64) */ + recoveryPrivateKey: string; }; }; responses: never; @@ -677,6 +820,36 @@ export interface operations { }; }; }; + EmailController_lookupRecipientKeys: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['LookupRecipientKeysRequestDto']; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['LookupRecipientKeysResponseDto']; + }; + }; + /** @description Invalid request: 1-50 valid emails required */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; EmailController_send: { parameters: { query?: never; @@ -725,7 +898,33 @@ export interface operations { }; }; }; - GatewayController_provisionAccount: { + UserController_getMailAccount: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['MailAccountStatusResponseDto']; + }; + }; + /** @description No mail account exists for the caller */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + UserController_createMailAccount: { parameters: { query?: never; header?: never; @@ -734,11 +933,34 @@ export interface operations { }; requestBody: { content: { - 'application/json': components['schemas']['ProvisionAccountRequestDto']; + 'application/json': components['schemas']['CreateMailAccountDto']; }; }; responses: { 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['CreateMailAccountResponseDto']; + }; + }; + /** @description Caller`s tier does not include mail access */ + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Requested domain does not exist */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Caller already has an account, or the address is taken */ + 409: { headers: { [name: string]: unknown; }; @@ -746,9 +968,43 @@ export interface operations { }; }; }; - GatewayController_listDomains: { + UserController_getMailAccountKeys: { parameters: { - query?: never; + query?: { + /** @description Address whose keys to fetch. Defaults to the caller`s primary address. */ + address?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['MailAccountKeysResponseDto']; + }; + }; + /** @description Address not found on this account, or keys not set */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + AddressesController_checkAvailability: { + parameters: { + query: { + /** @description Local part of the email address (before the @) */ + username: string; + /** @description Email domain */ + domain: string; + }; header?: never; path?: never; cookie?: never; @@ -763,6 +1019,25 @@ export interface operations { }; }; }; + GatewayController_getAddress: { + parameters: { + query?: never; + header?: never; + path: { + address: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; GatewayController_suspendAccount: { parameters: { query?: never; diff --git a/src/mail/types.ts b/src/mail/types.ts index 8a986510..0e1e8687 100644 --- a/src/mail/types.ts +++ b/src/mail/types.ts @@ -6,6 +6,11 @@ export type EmailListResponse = components['schemas']['EmailListResponseDto']; export type EmailResponse = components['schemas']['EmailResponseDto']; export type EmailCreatedResponse = components['schemas']['EmailCreatedResponseDto']; export type SendEmailRequest = components['schemas']['SendEmailRequestDto']; +export type EncryptionBlock = components['schemas']['EncryptionBlockDto']; +export type EncryptedWrappedKey = components['schemas']['EncryptedWrappedKeyDto']; +export type LookupRecipientKeysRequest = components['schemas']['LookupRecipientKeysRequestDto']; +export type LookupRecipientKeysResponse = components['schemas']['LookupRecipientKeysResponseDto']; +export type RecipientKey = components['schemas']['RecipientKeyDto']; export type DraftEmailRequest = components['schemas']['DraftEmailRequestDto']; export type UpdateEmailRequest = components['schemas']['UpdateEmailRequestDto']; export type EmailAddress = components['schemas']['EmailAddressDto'];