From 161554448a9bf97c319fc5fb16c5c64421ce16b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Egidijus=20Jucevi=C4=8Dius?= Date: Tue, 1 Sep 2026 10:34:40 +0300 Subject: [PATCH 1/3] feat(types): add html and text fields to SendEmailParams MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Types-only change; runtime dispatch is unchanged (Proxy passes all fields through as-is). Backward compatible: body keeps its existing meaning but is relaxed from required to optional because html or text can now stand in for it. - Adds html (alias of body, same HTML slot) and text (plain-text part) - Corrects body's JSDoc, which described it as plain text when it has always been sent as HTML (text/html via SendGrid) - Documents the multipart/alternative behaviour and all 422 cases - Adds type-level tests for all accepted combinations - Bumps version 0.8.44 → 0.8.45 Backend PR: base44-dev/apper#22289 (sendmail-plain-text) Note: template_id, template_name, variables are also missing from SendEmailParams — flagged for separate scoping with email-templates owners. Co-Authored-By: Claude Sonnet 4.6 --- package-lock.json | 4 +-- package.json | 2 +- src/modules/integrations.types.ts | 23 ++++++++++++-- tests/types/integrations.types.ts | 53 +++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 tests/types/integrations.types.ts diff --git a/package-lock.json b/package-lock.json index b36ae31a..7d2c4307 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@base44/sdk", - "version": "0.8.44", + "version": "0.8.45", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@base44/sdk", - "version": "0.8.44", + "version": "0.8.45", "license": "MIT", "dependencies": { "axios": "^1.18.1", diff --git a/package.json b/package.json index e4afe3fb..5d1b43a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@base44/sdk", - "version": "0.8.44", + "version": "0.8.45", "description": "JavaScript SDK for Base44 API", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/modules/integrations.types.ts b/src/modules/integrations.types.ts index 89369e7e..033fc495 100644 --- a/src/modules/integrations.types.ts +++ b/src/modules/integrations.types.ts @@ -89,14 +89,33 @@ export interface UploadFileResult { /** * Parameters for the SendEmail function. + * + * At least one of `body`, `html`, or `text` is required; the backend returns 422 otherwise. + * + * | Fields set | Result | + * |---|---| + * | `body` or `html` alone | `text/html` email | + * | `text` alone | `text/plain` email | + * | `body`/`html` + `text` | `multipart/alternative` — one email, two representations; recipient sees whichever their client prefers — both parts must say the same thing | + * | `body` + `html` | **422** — same slot, set one not both | */ export interface SendEmailParams { /** Recipient email address. */ to: string; /** Email subject line. */ subject: string; - /** Plain text email body content. */ - body: string; + /** HTML email body content. Alias of `html` — set one or the other, never both. */ + body?: string; + /** HTML email body content. The same thing as `body` under an explicit name. */ + html?: string; + /** + * Plain-text email body content. + * + * On its own, sends a plain-text (`text/plain`) email. Alongside `body`/`html`, + * both parts go out as a single `multipart/alternative` email and the recipient's + * mail client renders whichever it prefers — so the two must say the same thing. + */ + text?: string; /** The name of the sender. If omitted, the app's name will be used. */ from_name?: string; } diff --git a/tests/types/integrations.types.ts b/tests/types/integrations.types.ts new file mode 100644 index 00000000..e8ed5f0c --- /dev/null +++ b/tests/types/integrations.types.ts @@ -0,0 +1,53 @@ +import type { SendEmailParams } from "../../src/index.js"; + +// body-only (existing callers must still compile — backward compat) +const bodyOnly = { + to: "user@example.com", + subject: "Hello", + body: "

Hello

", +} satisfies SendEmailParams; + +// html-only +const htmlOnly = { + to: "user@example.com", + subject: "Hello", + html: "

Hello

", +} satisfies SendEmailParams; + +// text-only +const textOnly = { + to: "user@example.com", + subject: "Hello", + text: "Hello", +} satisfies SendEmailParams; + +// html + text → multipart/alternative +const htmlAndText = { + to: "user@example.com", + subject: "Hello", + html: "

Hello

", + text: "Hello", +} satisfies SendEmailParams; + +// body + text → multipart/alternative +const bodyAndText = { + to: "user@example.com", + subject: "Hello", + body: "

Hello

", + text: "Hello", +} satisfies SendEmailParams; + +// optional from_name +const withFromName = { + to: "user@example.com", + subject: "Hello", + body: "

Hello

", + from_name: "My App", +} satisfies SendEmailParams; + +void bodyOnly; +void htmlOnly; +void textOnly; +void htmlAndText; +void bodyAndText; +void withFromName; From 9e805ac8a01fe1b67d3042d09c603a83a02731b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Egidijus=20Jucevi=C4=8Dius?= Date: Tue, 1 Sep 2026 11:09:55 +0300 Subject: [PATCH 2/3] fix(types): enforce at least one content field in SendEmailParams Uses RequireAtLeastOne to make body/html/text collectively required at the type level. Omitting all three is now a compile error instead of a silent 422 at runtime. Adds a @ts-expect-error test to pin the behaviour. Co-Authored-By: Claude Sonnet 4.6 --- src/modules/integrations.types.ts | 33 +++++++++++++++++++------------ tests/types/integrations.types.ts | 5 +++++ 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/modules/integrations.types.ts b/src/modules/integrations.types.ts index 033fc495..0d160520 100644 --- a/src/modules/integrations.types.ts +++ b/src/modules/integrations.types.ts @@ -87,19 +87,12 @@ export interface UploadFileResult { file_url: string; } -/** - * Parameters for the SendEmail function. - * - * At least one of `body`, `html`, or `text` is required; the backend returns 422 otherwise. - * - * | Fields set | Result | - * |---|---| - * | `body` or `html` alone | `text/html` email | - * | `text` alone | `text/plain` email | - * | `body`/`html` + `text` | `multipart/alternative` — one email, two representations; recipient sees whichever their client prefers — both parts must say the same thing | - * | `body` + `html` | **422** — same slot, set one not both | - */ -export interface SendEmailParams { +/** Requires at least one key from Keys to be present. */ +type RequireAtLeastOne = + Pick> & + { [K in Keys]-?: Required> & Partial>> }[Keys]; + +interface SendEmailParamsBase { /** Recipient email address. */ to: string; /** Email subject line. */ @@ -120,6 +113,20 @@ export interface SendEmailParams { from_name?: string; } +/** + * Parameters for the SendEmail function. + * + * At least one of `body`, `html`, or `text` is required; the backend returns 422 otherwise. + * + * | Fields set | Result | + * |---|---| + * | `body` or `html` alone | `text/html` email | + * | `text` alone | `text/plain` email | + * | `body`/`html` + `text` | `multipart/alternative` — one email, two representations; recipient sees whichever their client prefers — both parts must say the same thing | + * | `body` + `html` | **422** — same slot, set one not both | + */ +export type SendEmailParams = RequireAtLeastOne; + export type SendEmailResult = any; /** diff --git a/tests/types/integrations.types.ts b/tests/types/integrations.types.ts index e8ed5f0c..bc17f3e5 100644 --- a/tests/types/integrations.types.ts +++ b/tests/types/integrations.types.ts @@ -45,9 +45,14 @@ const withFromName = { from_name: "My App", } satisfies SendEmailParams; +// omitting all three content fields must be a compile error +// @ts-expect-error At least one of body/html/text is required. +const missingContent: SendEmailParams = { to: "user@example.com", subject: "Hello" }; + void bodyOnly; void htmlOnly; void textOnly; void htmlAndText; void bodyAndText; void withFromName; +void missingContent; From fdb36b10391fd15a2316f194ac5d05e5b1166f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Egidijus=20Jucevi=C4=8Dius?= Date: Wed, 9 Sep 2026 19:10:04 +0300 Subject: [PATCH 3/3] feat(types): add EmailAttachment and attachments field to SendEmailParams Mirrors base44-dev/apper#22544. Each attachment carries a filename and exactly one source: content (inline base64) or file_url (storage reference). Limits: up to 5 attachments, 5 MB each, 10 MB total. Allowed extensions documented in JSDoc. EmailAttachment exported from package index. Co-Authored-By: Claude Sonnet 4.6 --- src/index.ts | 1 + src/modules/integrations.types.ts | 21 +++++++++++++++++++++ tests/types/integrations.types.ts | 25 ++++++++++++++++++++++++- 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 6886671b..16273cfd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -70,6 +70,7 @@ export type { GenerateImageResult, UploadFileParams, UploadFileResult, + EmailAttachment, SendEmailParams, SendEmailResult, ExtractDataFromUploadedFileParams, diff --git a/src/modules/integrations.types.ts b/src/modules/integrations.types.ts index 0d160520..6f013bd2 100644 --- a/src/modules/integrations.types.ts +++ b/src/modules/integrations.types.ts @@ -92,6 +92,20 @@ type RequireAtLeastOne = Pick> & { [K in Keys]-?: Required> & Partial>> }[Keys]; +/** + * A file to attach to a `SendEmail` call. + * + * Provide exactly one content source per attachment: + * - `content` — base64-encoded bytes generated at runtime (e.g. a PDF built in the function). + * - `file_url` — a URL returned by `UploadFile` or `UploadPrivateFile` for a file already in storage. + * + * Allowed extensions: `pdf`, `png`, `jpg`/`jpeg`, `gif`, `webp`, `csv`, `txt`, `md`, `ics`, `xlsx`, `docx`. + * Limits: up to **5 attachments**, **5 MB each**, **10 MB total**. + */ +export type EmailAttachment = + | { /** Attachment filename, including extension. */ filename: string; /** Base64-encoded file content. */ content: string; file_url?: never } + | { /** Attachment filename, including extension. */ filename: string; /** URL from `UploadFile` or `UploadPrivateFile`. */ file_url: string; content?: never }; + interface SendEmailParamsBase { /** Recipient email address. */ to: string; @@ -111,6 +125,13 @@ interface SendEmailParamsBase { text?: string; /** The name of the sender. If omitted, the app's name will be used. */ from_name?: string; + /** + * Files to attach to the email. Up to 5 attachments, 5 MB each, 10 MB total. + * + * Each item must have a `filename` and exactly one content source: + * `content` (inline base64) or `file_url` (storage reference). + */ + attachments?: EmailAttachment[]; } /** diff --git a/tests/types/integrations.types.ts b/tests/types/integrations.types.ts index bc17f3e5..b74dabb5 100644 --- a/tests/types/integrations.types.ts +++ b/tests/types/integrations.types.ts @@ -1,4 +1,4 @@ -import type { SendEmailParams } from "../../src/index.js"; +import type { SendEmailParams, EmailAttachment } from "../../src/index.js"; // body-only (existing callers must still compile — backward compat) const bodyOnly = { @@ -45,6 +45,26 @@ const withFromName = { from_name: "My App", } satisfies SendEmailParams; +// attachments: inline base64 +const inlineAttachment = { + filename: "receipt.pdf", + content: "JVBERi0x", +} satisfies EmailAttachment; + +// attachments: storage reference +const storedAttachment = { + filename: "logo.png", + file_url: "https://storage.example.com/logo.png", +} satisfies EmailAttachment; + +// with attachments on a full params object +const withAttachments = { + to: "user@example.com", + subject: "Invoice", + body: "

See attached.

", + attachments: [inlineAttachment, storedAttachment], +} satisfies SendEmailParams; + // omitting all three content fields must be a compile error // @ts-expect-error At least one of body/html/text is required. const missingContent: SendEmailParams = { to: "user@example.com", subject: "Hello" }; @@ -55,4 +75,7 @@ void textOnly; void htmlAndText; void bodyAndText; void withFromName; +void inlineAttachment; +void storedAttachment; +void withAttachments; void missingContent;