@mindstudio-ai/agent 0.1.109 → 0.1.111

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/dist/index.d.ts CHANGED
@@ -2238,6 +2238,15 @@ interface Citation {
2238
2238
  }
2239
2239
  /** A metadata value as stored on a document: scalars only. */
2240
2240
  type MetadataValue = string | number | boolean;
2241
+ /**
2242
+ * Numeric range over a metadata value (inclusive both ends). Numbers only —
2243
+ * to range on dates, store them as sortable integers at add time (epoch
2244
+ * seconds or `20240315`-style YYYYMMDD) and range on those.
2245
+ */
2246
+ interface MetadataRange {
2247
+ gte?: number;
2248
+ lte?: number;
2249
+ }
2241
2250
  /**
2242
2251
  * Tags attached to a document when it's added — department, year, doc type,
2243
2252
  * a per-user scope — and matched by {@link SearchFilter.metadata} at query
@@ -2261,10 +2270,11 @@ type SearchMode = 'hybrid' | 'semantic' | 'lexical';
2261
2270
  interface SearchFilter {
2262
2271
  /**
2263
2272
  * Match document metadata set at add time. A scalar must equal; an array
2264
- * matches any of its values. Keys AND together:
2265
- * `{ department: 'legal', year: [2025, 2026] }`.
2273
+ * matches any of its values; `{ gte?, lte? }` matches a numeric range. Keys
2274
+ * AND together:
2275
+ * `{ department: 'legal', year: [2025, 2026], publishedAt: { gte: 20250101 } }`.
2266
2276
  */
2267
- metadata?: Record<string, MetadataValue | MetadataValue[]>;
2277
+ metadata?: Record<string, MetadataValue | MetadataValue[] | MetadataRange>;
2268
2278
  /** Exact filename, or any of several. */
2269
2279
  filename?: string | string[];
2270
2280
  /** Restrict to specific documents (ids from {@link DataSource.documents}). */
@@ -2297,7 +2307,16 @@ interface SearchExplain {
2297
2307
  matchedVia: 'dense' | 'lexical' | 'both';
2298
2308
  }
2299
2309
  interface SearchHit {
2300
- /** Provider relevance score. Comparable within a response, not across them. */
2310
+ /**
2311
+ * Relevance of this hit. Comparable within a response, not across them —
2312
+ * the scale depends on how the search ran (check {@link SearchRan}):
2313
+ * cosine similarity in `semantic` mode, an RRF rank reciprocal in `hybrid`
2314
+ * (small numbers that are not similarities), keyword-overlap weight in
2315
+ * `lexical`. When reranking ran, it's the reranker's 0–1 relevance instead,
2316
+ * with the retrieval value preserved as `retrievalScore` — so rerank-on
2317
+ * `score` is the one scale that's stable across modes, and the right place
2318
+ * for quality cutoffs.
2319
+ */
2301
2320
  score: number;
2302
2321
  /** The matched chunk, prefixed with its heading path for context. */
2303
2322
  text: string;
@@ -2359,7 +2378,7 @@ interface SearchRan {
2359
2378
  * vector, so they take effect on the next call and cost nothing. Anything that
2360
2379
  * would require rebuilding the corpus — chunking, the embedding model, whether
2361
2380
  * chunks are contextualized — is a property of the data source, configured
2362
- * with `mindstudio-prod datasources config` rather than passed here.
2381
+ * with `remy-admin datasources config` rather than passed here.
2363
2382
  *
2364
2383
  * The defaults come from the data source's own configuration, so most callers
2365
2384
  * should pass nothing.
@@ -2367,7 +2386,14 @@ interface SearchRan {
2367
2386
  interface SearchOptions {
2368
2387
  /** Results to return. Default 5, capped at 50. */
2369
2388
  topK?: number;
2370
- /** Drop hits below this score. Provider-specific scale — measure before using. */
2389
+ /**
2390
+ * Floor on the RETRIEVAL score — applied to the retrieval branch, never to
2391
+ * the fused hybrid score or the reranker's score. In `semantic` and
2392
+ * `hybrid` modes it's a cosine floor on the embedding branch; in `lexical`,
2393
+ * a keyword-overlap floor. The scale depends on the embedding model, so
2394
+ * measure before using — and with reranking on, prefer cutting on each
2395
+ * hit's returned `score` (the reranker's 0–1 relevance) instead.
2396
+ */
2371
2397
  scoreThreshold?: number;
2372
2398
  /**
2373
2399
  * Narrow the search to matching chunks before ranking — by document
@@ -2869,12 +2895,16 @@ interface Analytics {
2869
2895
  }
2870
2896
 
2871
2897
  /**
2872
- * The `email` namespace: read and manage your app's OWN outbound email.
2898
+ * The `email` namespace: send, read and manage your app's OWN outbound email.
2899
+ *
2900
+ * `email.send()` is the send path. The older `mindstudio.sendEmail({...})` action
2901
+ * still works and shares exactly the same implementation, so nothing breaks — but
2902
+ * `send()` is the one to reach for: it returns a proper receipt (including the
2903
+ * `batchId` you read stats back with) and it lives alongside everything you do
2904
+ * with the result.
2873
2905
  *
2874
- * Sending is not here — that's the `sendEmail` action
2875
- * (`mindstudio.sendEmail({...})`). This namespace is everything *after* the send:
2876
- * what happened to each message, how a campaign performed, and who has
2877
- * unsubscribed.
2906
+ * The rest of the namespace is everything *after* the send: what happened to each
2907
+ * message, how a campaign performed, and who has unsubscribed.
2878
2908
  *
2879
2909
  * The point is that you can build your own email admin screen inside your own app
2880
2910
  * and never open the MindStudio dashboard. So this deliberately includes the
@@ -2882,7 +2912,7 @@ interface Analytics {
2882
2912
  * unsubscribe list, this can do too.
2883
2913
  *
2884
2914
  * Every call is scoped to the executing app by its request token; there is no
2885
- * appId parameter and no way to read another app's mail.
2915
+ * appId parameter and no way to read or send as another app.
2886
2916
  */
2887
2917
  /** Where a message got to. See `EmailMessage.status`. */
2888
2918
  type EmailMessageStatus =
@@ -2946,6 +2976,17 @@ interface EmailStats {
2946
2976
  counts: Record<string, number>;
2947
2977
  }>;
2948
2978
  }
2979
+ /**
2980
+ * Lifecycle of a QUEUED send. Large and marketing sends are accepted
2981
+ * immediately and delivered in the background, so a blast can legitimately be
2982
+ * observed mid-flight:
2983
+ *
2984
+ * `accepted` — queued, nothing delivered yet. `sending` — delivery in
2985
+ * progress. `completed` — every recipient was handed to the provider.
2986
+ * `partial` — finished, but some recipients were not sent to. `failed` —
2987
+ * finished and nothing was sent.
2988
+ */
2989
+ type EmailBatchLifecycle = 'accepted' | 'sending' | 'completed' | 'partial' | 'failed';
2949
2990
  /**
2950
2991
  * One blast, aggregated.
2951
2992
  *
@@ -2959,6 +3000,17 @@ interface EmailBatch {
2959
3000
  recipients: number;
2960
3001
  accepted: number;
2961
3002
  counts: Record<EmailMessageStatus, number>;
3003
+ /**
3004
+ * Delivery lifecycle, when the send was QUEUED. `null` means the send was
3005
+ * delivered inline (small transactional sends) and already finished — treat
3006
+ * null as done, never as unknown.
3007
+ */
3008
+ status: EmailBatchLifecycle | null;
3009
+ /** Delivery-chunk progress, when queued. Null otherwise. */
3010
+ chunksTotal: number | null;
3011
+ chunksDone: number | null;
3012
+ /** Recipients not yet attempted, when queued. Null otherwise. */
3013
+ pending: number | null;
2962
3014
  }
2963
3015
  interface EmailSuppression {
2964
3016
  id: string;
@@ -3018,7 +3070,155 @@ interface EmailWindowOptions {
3018
3070
  start?: string | Date;
3019
3071
  end?: string | Date;
3020
3072
  }
3073
+ /**
3074
+ * How a message body is interpreted.
3075
+ *
3076
+ * `auto` (the default) sniffs it: something that looks like HTML is sent as HTML,
3077
+ * anything else is rendered from Markdown. Every message goes out multipart with a
3078
+ * plain-text alternative derived from the body, which mail providers reward.
3079
+ */
3080
+ type EmailBodyType = 'auto' | 'html' | 'markdown' | 'text';
3081
+ interface EmailAttachmentInput {
3082
+ url: string;
3083
+ /** Displayed filename. Derived from the URL when omitted. */
3084
+ filename?: string;
3085
+ contentType?: string;
3086
+ }
3087
+ interface EmailSendOptions {
3088
+ /** Visible recipients. Optional only if you supply `cc` or `bcc`. */
3089
+ to?: string | string[];
3090
+ cc?: string | string[];
3091
+ /**
3092
+ * Hidden recipients. With no `to`/`cc`, the To: header is addressed to your
3093
+ * app's own sender — the standard "undisclosed recipients" pattern — so
3094
+ * recipients can't see each other.
3095
+ */
3096
+ bcc?: string | string[];
3097
+ subject: string;
3098
+ /** Plain text, Markdown, or HTML. See {@link EmailBodyType}. */
3099
+ body: string;
3100
+ bodyType?: EmailBodyType;
3101
+ /** Your own plain-text alternative, instead of the auto-derived one. */
3102
+ text?: string;
3103
+ attachments?: Array<string | EmailAttachmentInput>;
3104
+ /**
3105
+ * Sender handle — only if your app has a custom domain or platform subdomain.
3106
+ * `"support"`, `"support@your-domain.com"`, or `"Name <support@your-domain.com>"`.
3107
+ * The domain must be one your app owns or the send fails.
3108
+ */
3109
+ from?: string;
3110
+ replyTo?: string;
3111
+ /** Message-ID this replies to, for threading in a shared inbox. */
3112
+ inReplyTo?: string;
3113
+ /** Prior Message-IDs in the thread. */
3114
+ references?: string[];
3115
+ /**
3116
+ * **Defaults to `marketing`**, which is the consequential choice here.
3117
+ *
3118
+ * `marketing` attaches a one-click unsubscribe header pointing at your own
3119
+ * domain and skips anyone who previously unsubscribed from your app. Because
3120
+ * that link is per-recipient, a marketing send delivers one separate message per
3121
+ * recipient and `cc`/`bcc` are folded into that list — so they come back empty.
3122
+ *
3123
+ * `transactional` is for receipts, alerts and password resets: no unsubscribe
3124
+ * header, the unsubscribe list is ignored, and `cc`/`bcc` stay on one shared
3125
+ * message. Use it whenever cc/bcc semantics actually matter.
3126
+ */
3127
+ category?: 'transactional' | 'marketing';
3128
+ /**
3129
+ * Groups this send's per-recipient rows into one blast. Pass your own campaign
3130
+ * id (1–64 printable chars) to join our delivery stats onto your own tables with
3131
+ * no mapping; one is generated when omitted, and it's always returned.
3132
+ */
3133
+ batchId?: string;
3134
+ }
3135
+ interface EmailSendResult {
3136
+ /** Addresses the message went to. */
3137
+ recipients: string[];
3138
+ /** Always empty for a marketing send — see `category`. */
3139
+ cc: string[];
3140
+ /** Always empty for a marketing send — see `category`. */
3141
+ bcc: string[];
3142
+ /** The resolved sender it went out as. */
3143
+ from: string;
3144
+ /**
3145
+ * Recipients skipped because they had unsubscribed from your app. **Not an
3146
+ * error** — the send succeeded and we honoured their opt-out. Always empty for
3147
+ * a transactional send.
3148
+ *
3149
+ * Worth surfacing to whoever triggered the send: "sent to 48 of 50, 2
3150
+ * unsubscribed" is the honest report, and silence here is what makes people
3151
+ * think mail vanished.
3152
+ */
3153
+ suppressed: string[];
3154
+ /** Read stats back with `email.batch(batchId)`. */
3155
+ batchId: string;
3156
+ }
3157
+ /** Your app's sending allowance for the current window. */
3158
+ interface EmailQuota {
3159
+ /** What the numbers count — `recipients`. */
3160
+ unit: string;
3161
+ window: 'day' | 'month' | 'total';
3162
+ /** `null` means unlimited. */
3163
+ limit: number | null;
3164
+ used: number;
3165
+ /** `null` when `limit` is null. */
3166
+ remaining: number | null;
3167
+ /** When `used` resets. */
3168
+ resetsAt: string | null;
3169
+ }
3021
3170
  interface Email {
3171
+ /**
3172
+ * Send an email.
3173
+ *
3174
+ * Two things to know before your first call. **`category` defaults to
3175
+ * `marketing`**, which fans a multi-recipient send out into one message each and
3176
+ * empties `cc`/`bcc` — pass `'transactional'` for receipts, alerts and codes.
3177
+ * And a non-empty `suppressed` in the result is a **success**, not a failure: it
3178
+ * lists people who had unsubscribed and were therefore skipped.
3179
+ *
3180
+ * Delivery is split from acceptance: small transactional sends deliver before
3181
+ * this resolves; marketing and large sends are QUEUED — this resolves once the
3182
+ * send is accepted, and delivery continues in the background. Track progress
3183
+ * with `email.batch(batchId)` (`status`, `pending`, chunk counts); outcomes
3184
+ * (delivered, bounced, complained) arrive later on `email.messages({ batchId })`.
3185
+ *
3186
+ * Throws `outbound_daily_cap_exceeded` (429) over the app's daily recipient
3187
+ * quota — check `email.quota()` before a big send — and `sending_paused` (409)
3188
+ * when the app's sending is paused for reputation or by support; a 409 will
3189
+ * not succeed on retry.
3190
+ *
3191
+ * @example
3192
+ * ```ts
3193
+ * // A receipt: one message, cc preserved, no unsubscribe header.
3194
+ * await email.send({
3195
+ * to: 'customer@example.com',
3196
+ * cc: 'billing@your-co.com',
3197
+ * subject: 'Your receipt',
3198
+ * body: '# Thanks!\n\nOrder #1234 is confirmed.',
3199
+ * category: 'transactional',
3200
+ * });
3201
+ *
3202
+ * // A campaign: per-recipient unsubscribe, your own id for joining stats.
3203
+ * const { suppressed, batchId } = await email.send({
3204
+ * to: subscribers,
3205
+ * subject: 'August newsletter',
3206
+ * body: markdown,
3207
+ * batchId: 'newsletter-2026-08',
3208
+ * });
3209
+ * console.log(`skipped ${suppressed.length} unsubscribed`);
3210
+ * const stats = await email.batch(batchId);
3211
+ * ```
3212
+ */
3213
+ send(options: EmailSendOptions): Promise<EmailSendResult>;
3214
+ /**
3215
+ * Your app's sending allowance and how much is left.
3216
+ *
3217
+ * Useful before a large send: check `remaining` and split the list rather than
3218
+ * discovering the limit as a failure partway through. Going over throws with
3219
+ * `outbound_daily_cap_exceeded`.
3220
+ */
3221
+ quota(): Promise<EmailQuota>;
3022
3222
  /** Counts, rates and a timeseries over a window (default: last 24h). */
3023
3223
  stats(options?: EmailWindowOptions & {
3024
3224
  buckets?: number;
@@ -3038,7 +3238,7 @@ interface Email {
3038
3238
  }>;
3039
3239
  /** One message, including bounce diagnostics. */
3040
3240
  message(messageId: string): Promise<EmailMessage>;
3041
- /** One row per blast, newest first. */
3241
+ /** One row per blast, newest first, with delivery lifecycle where queued. */
3042
3242
  batches(options?: EmailWindowOptions & {
3043
3243
  limit?: number;
3044
3244
  offset?: number;
@@ -3046,7 +3246,11 @@ interface Email {
3046
3246
  batches: EmailBatch[];
3047
3247
  total: number;
3048
3248
  }>;
3049
- /** Stats for a single blast — the id you passed to `sendEmail`, or the one it returned. */
3249
+ /**
3250
+ * Stats and delivery progress for a single blast — the id you passed to
3251
+ * `email.send()`, or the one it returned. Poll this to watch a queued send
3252
+ * drain (`status`, `pending`, chunk counts).
3253
+ */
3050
3254
  batch(batchId: string): Promise<EmailBatch>;
3051
3255
  /** Your app's unsubscribe list, newest first. */
3052
3256
  suppressions(options?: {
@@ -3738,13 +3942,25 @@ declare class MindStudioAgent$1 {
3738
3942
  */
3739
3943
  get analytics(): Analytics;
3740
3944
  /**
3741
- * Outbound email: the per-recipient delivery log, blast stats, and this app's
3742
- * unsubscribe list. Sending is the `sendEmail` action, not this namespace.
3945
+ * Outbound email: sending, the per-recipient delivery log, blast stats, and
3946
+ * this app's unsubscribe list.
3947
+ *
3948
+ * `email.send()` is the send path — the `sendEmail` action still works and runs
3949
+ * the same code, but `send()` returns a receipt including the `batchId` you read
3950
+ * stats back with. Note `category` defaults to `'marketing'`; see
3951
+ * {@link Email.send}.
3743
3952
  *
3744
3953
  * @example
3745
3954
  * ```ts
3746
3955
  * import { email } from '@mindstudio-ai/agent';
3747
3956
  *
3957
+ * await email.send({
3958
+ * to: 'a@b.com',
3959
+ * subject: 'Your receipt',
3960
+ * body: 'Thanks!',
3961
+ * category: 'transactional',
3962
+ * });
3963
+ *
3748
3964
  * // A support lookup: did this person get their mail?
3749
3965
  * const { messages } = await email.messages({ recipient: 'a@b.com' });
3750
3966
  *
@@ -7219,6 +7435,8 @@ interface SendEmailStepInput {
7219
7435
  references?: string[];
7220
7436
  /** `marketing` (default) adds one-click unsubscribe headers and skips recipients who unsubscribed from this app. Use `transactional` for receipts, alerts and password resets: no unsubscribe header, never suppressed, and cc/bcc preserved on a single message. */
7221
7437
  category?: "transactional" | "marketing";
7438
+ /** Groups this send's per-recipient rows into one blast, so per-campaign stats are readable afterwards. Pass your own campaign id (1-64 printable chars) to join our delivery stats onto your own tables with no mapping; omit it and one is generated. Either way it comes back in the output. */
7439
+ batchId?: string;
7222
7440
  }
7223
7441
  interface SendEmailStepOutput {
7224
7442
  /** To addresses the message was sent to. */
@@ -7231,6 +7449,8 @@ interface SendEmailStepOutput {
7231
7449
  from: string;
7232
7450
  /** Recipients skipped because they unsubscribed from this app. Always empty for a `transactional` send. */
7233
7451
  suppressed: string[];
7452
+ /** Groups this send's per-recipient rows into one blast — the caller's `batchId` if supplied, otherwise a generated one. Returned unconditionally so an app can read per-blast stats back without having had to pass anything. */
7453
+ batchId: string;
7234
7454
  }
7235
7455
  interface SendGmailDraftStepInput {
7236
7456
  /** Gmail draft ID to send */
@@ -10318,6 +10538,7 @@ interface StepMethods {
10318
10538
  * Send an email to one or more recipient addresses.
10319
10539
  *
10320
10540
  * @remarks
10541
+ * - If you are writing a backend method for a v2 app, prefer mindstudio.email.send({...}) — it takes the same fields, returns the batchId you read stats back with, and sits alongside email.messages / email.batch / email.suppress. This action remains fully supported and runs the same code; it is also the only way to use connectionId or generateHtml.
10321
10542
  * - Use the "to" field to send to one or more specific recipient email addresses directly. Allowed recipients depend on the sender: when the app sends from a domain it owns (a verified custom domain or its <slug>.madewithremy.com subdomain) any recipient is allowed; when it falls back to the shared Remy address, recipients must be verified app users or members of the app's organization. (v1 apps cannot use a direct "to" — they must resolve recipients via a connection.)
10322
10543
  * - Alternatively, recipient email addresses can be resolved from OAuth connections configured by the app creator via connectionId. The user running the workflow does not specify the recipient directly.
10323
10544
  * - Use "cc" and "bcc" to add visible / hidden recipients (a string or an array). They are subject to the same recipient rules as "to".
@@ -10338,7 +10559,7 @@ interface StepMethods {
10338
10559
  * - The special connectionId "trigger_email" uses the email address that triggered the workflow.
10339
10560
  * - Sends default to category "marketing": every message carries a one-click unsubscribe header pointing at the app's own domain, and recipients who previously unsubscribed from this app are skipped and returned in "suppressed". Because the unsubscribe link is per-recipient, a marketing send delivers one separate message per recipient and cc/bcc are folded into that list.
10340
10561
  * - Set category to "transactional" for receipts, alerts, password resets and anything a recipient must not be able to opt out of. Transactional sends carry no unsubscribe header, ignore the unsubscribe list, and keep cc/bcc on a single message — use it whenever cc/bcc semantics matter.
10341
- * - The subject is stored verbatim in the app's delivery log, which every workspace member can read for 90 days. Never put a secret in the subject — no one-time codes, no magic-link tokens, no password-reset tokens. Put those in the body, which is never stored.
10562
+ * - The subject is stored verbatim in the app's delivery log, which every workspace member can read for 90 days. Never put a secret in the subject — no one-time codes, no magic-link tokens, no password-reset tokens. Put those in the body of a "transactional" send: a small transactional send is delivered immediately and its body is never stored. A "marketing" send, or any large send, is queued for delivery, which means its body is held in internal storage until the send finishes — so do not put a secret in the body of one of those either.
10342
10563
  *
10343
10564
  * @example
10344
10565
  * ```typescript
@@ -11205,11 +11426,15 @@ declare const voice: Voice;
11205
11426
  */
11206
11427
  declare const analytics: Analytics;
11207
11428
  /**
11208
- * Your app's outbound email — the delivery log, blast stats, and the unsubscribe
11209
- * list. Sending is `mindstudio.sendEmail(...)`; this is everything after it.
11429
+ * Your app's outbound email — sending, the delivery log, blast stats, and the
11430
+ * unsubscribe list.
11431
+ *
11432
+ * `email.send()` is the send path. `mindstudio.sendEmail(...)` still works and
11433
+ * runs the same code underneath, but `send()` returns a proper receipt and sits
11434
+ * next to everything you do with the result.
11210
11435
  *
11211
11436
  * Scoped to the executing app by its request token, so there is no appId to pass
11212
- * and no way to read another app's mail.
11437
+ * and no way to read or send as another app.
11213
11438
  *
11214
11439
  * @example
11215
11440
  * ```ts
package/dist/index.js CHANGED
@@ -3026,6 +3026,12 @@ function createAnalytics(call) {
3026
3026
  var iso = (v) => v === void 0 ? void 0 : v instanceof Date ? v.toISOString() : v;
3027
3027
  function createEmail(call) {
3028
3028
  return {
3029
+ send(options) {
3030
+ return call("send", options);
3031
+ },
3032
+ quota() {
3033
+ return call("quota", {});
3034
+ },
3029
3035
  stats(options) {
3030
3036
  return call("stats", {
3031
3037
  start: iso(options?.start),
@@ -4030,7 +4036,8 @@ var stepMetadata = {
4030
4036
  "sendEmail": {
4031
4037
  stepType: "sendEmail",
4032
4038
  description: "Send an email to one or more recipient addresses.",
4033
- usageNotes: `- Use the "to" field to send to one or more specific recipient email addresses directly. Allowed recipients depend on the sender: when the app sends from a domain it owns (a verified custom domain or its <slug>.madewithremy.com subdomain) any recipient is allowed; when it falls back to the shared Remy address, recipients must be verified app users or members of the app's organization. (v1 apps cannot use a direct "to" \u2014 they must resolve recipients via a connection.)
4039
+ usageNotes: `- If you are writing a backend method for a v2 app, prefer mindstudio.email.send({...}) \u2014 it takes the same fields, returns the batchId you read stats back with, and sits alongside email.messages / email.batch / email.suppress. This action remains fully supported and runs the same code; it is also the only way to use connectionId or generateHtml.
4040
+ - Use the "to" field to send to one or more specific recipient email addresses directly. Allowed recipients depend on the sender: when the app sends from a domain it owns (a verified custom domain or its <slug>.madewithremy.com subdomain) any recipient is allowed; when it falls back to the shared Remy address, recipients must be verified app users or members of the app's organization. (v1 apps cannot use a direct "to" \u2014 they must resolve recipients via a connection.)
4034
4041
  - Alternatively, recipient email addresses can be resolved from OAuth connections configured by the app creator via connectionId. The user running the workflow does not specify the recipient directly.
4035
4042
  - Use "cc" and "bcc" to add visible / hidden recipients (a string or an array). They are subject to the same recipient rules as "to".
4036
4043
  - "to" is optional only in the sense that recipients can come from elsewhere \u2014 omit it and supply "cc"/"bcc" for a hidden-list send, or omit all three and recipients are resolved from an OAuth connection. Naming nobody at all is an error.
@@ -4050,9 +4057,9 @@ var stepMetadata = {
4050
4057
  - The special connectionId "trigger_email" uses the email address that triggered the workflow.
4051
4058
  - Sends default to category "marketing": every message carries a one-click unsubscribe header pointing at the app's own domain, and recipients who previously unsubscribed from this app are skipped and returned in "suppressed". Because the unsubscribe link is per-recipient, a marketing send delivers one separate message per recipient and cc/bcc are folded into that list.
4052
4059
  - Set category to "transactional" for receipts, alerts, password resets and anything a recipient must not be able to opt out of. Transactional sends carry no unsubscribe header, ignore the unsubscribe list, and keep cc/bcc on a single message \u2014 use it whenever cc/bcc semantics matter.
4053
- - The subject is stored verbatim in the app's delivery log, which every workspace member can read for 90 days. Never put a secret in the subject \u2014 no one-time codes, no magic-link tokens, no password-reset tokens. Put those in the body, which is never stored.`,
4054
- inputSchema: { "type": "object", "properties": { "subject": { "type": "string", "description": "Email subject line" }, "body": { "type": "string", "description": "Email body content (plain text, markdown, HTML, or a CDN URL to an HTML file)" }, "to": { "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }] }, "cc": { "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }] }, "bcc": { "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }] }, "connectionId": { "type": "string", "description": "OAuth connection ID(s) for the recipient(s), comma-separated for multiple" }, "generateHtml": { "type": "boolean", "description": "When true, auto-convert the body text into a styled HTML email using AI" }, "generateHtmlInstructions": { "type": "string", "description": "Natural language instructions for the HTML generation style" }, "generateHtmlModelOverride": { "type": "object", "properties": { "model": { "type": "string", "description": 'Model identifier (e.g. "gpt-4", "claude-3-opus")' }, "temperature": { "type": "number", "description": "Sampling temperature for the model (0-2)" }, "maxResponseTokens": { "type": "number", "description": "Maximum number of tokens in the model's response" }, "ignorePreamble": { "type": "boolean", "description": "Whether to skip the system preamble/instructions" }, "userMessagePreprocessor": { "type": "object", "properties": { "dataSource": { "type": "string", "description": "Data source identifier for the preprocessor" }, "messageTemplate": { "type": "string", "description": "Template string applied to user messages before sending to the model" }, "maxResults": { "type": "number", "description": "Maximum number of results to include from the data source" }, "enabled": { "type": "boolean", "description": "Whether the preprocessor is active" }, "shouldInherit": { "type": "boolean", "description": "Whether child steps should inherit this preprocessor configuration" } }, "description": "Preprocessor applied to user messages before sending to the model" }, "preamble": { "type": "string", "description": "System preamble/instructions for the model" }, "multiModelEnabled": { "type": "boolean", "description": "Whether multi-model candidate generation is enabled" }, "editResponseEnabled": { "type": "boolean", "description": "Whether the user can edit the model's response" }, "config": { "type": "object", "properties": {}, "required": [], "description": "Additional model-specific configuration" } }, "required": ["model", "temperature", "maxResponseTokens"], "description": "Model settings override for HTML generation" }, "attachments": { "type": "array", "items": { "anyOf": [{ "type": "string" }, { "type": "object", "properties": { "url": { "type": "string" }, "filename": { "type": "string" }, "contentType": { "type": "string" } }, "required": ["url"] }] }, "description": "Files to attach: each entry is a URL string, or an object `{ url, filename?, contentType? }` to control the displayed filename and MIME type." }, "from": { "type": "string", "description": "Custom sender handle \u2014 only for apps with a custom domain or subdomain. Bare handle (`support`), full `support@your-domain.com`, or `Name <support@your-domain.com>`. Must resolve to a domain the app owns." }, "bodyType": { "enum": ["auto", "html", "markdown", "text"], "type": "string", "description": "How to interpret `body`: `auto` (default \u2014 detect HTML, else render markdown), `html` (send as-is), `markdown` (render to HTML), or `text` (send as plain text only, no HTML part). Every send includes a text/plain alternative." }, "text": { "type": "string", "description": "Explicit text/plain alternative body. Auto-derived from `body` if omitted." }, "replyTo": { "type": "string", "description": "Reply-To address for the email." }, "inReplyTo": { "type": "string", "description": "Message-ID this email replies to, for inbox threading (In-Reply-To header)." }, "references": { "type": "array", "items": { "type": "string" }, "description": "Prior Message-IDs in the thread, for inbox threading (References header)." }, "category": { "enum": ["transactional", "marketing"], "type": "string", "description": "`marketing` (default) adds one-click unsubscribe headers and skips recipients who unsubscribed from this app. Use `transactional` for receipts, alerts and password resets: no unsubscribe header, never suppressed, and cc/bcc preserved on a single message." } }, "required": ["subject", "body"] },
4055
- outputSchema: { "type": "object", "properties": { "recipients": { "type": "array", "items": { "type": "string" }, "description": "To addresses the message was sent to." }, "cc": { "type": "array", "items": { "type": "string" }, "description": "Cc addresses on the message (empty if none)." }, "bcc": { "type": "array", "items": { "type": "string" }, "description": "Bcc addresses on the message (empty if none)." }, "from": { "type": "string", "description": "The resolved sender address the message went out as (auto-selected when no `from` is given)." }, "suppressed": { "type": "array", "items": { "type": "string" }, "description": "Recipients skipped because they unsubscribed from this app. Always empty for a `transactional` send." } }, "required": ["recipients", "cc", "bcc", "from", "suppressed"] }
4060
+ - The subject is stored verbatim in the app's delivery log, which every workspace member can read for 90 days. Never put a secret in the subject \u2014 no one-time codes, no magic-link tokens, no password-reset tokens. Put those in the body of a "transactional" send: a small transactional send is delivered immediately and its body is never stored. A "marketing" send, or any large send, is queued for delivery, which means its body is held in internal storage until the send finishes \u2014 so do not put a secret in the body of one of those either.`,
4061
+ inputSchema: { "type": "object", "properties": { "subject": { "type": "string", "description": "Email subject line" }, "body": { "type": "string", "description": "Email body content (plain text, markdown, HTML, or a CDN URL to an HTML file)" }, "to": { "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }] }, "cc": { "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }] }, "bcc": { "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }] }, "connectionId": { "type": "string", "description": "OAuth connection ID(s) for the recipient(s), comma-separated for multiple" }, "generateHtml": { "type": "boolean", "description": "When true, auto-convert the body text into a styled HTML email using AI" }, "generateHtmlInstructions": { "type": "string", "description": "Natural language instructions for the HTML generation style" }, "generateHtmlModelOverride": { "type": "object", "properties": { "model": { "type": "string", "description": 'Model identifier (e.g. "gpt-4", "claude-3-opus")' }, "temperature": { "type": "number", "description": "Sampling temperature for the model (0-2)" }, "maxResponseTokens": { "type": "number", "description": "Maximum number of tokens in the model's response" }, "ignorePreamble": { "type": "boolean", "description": "Whether to skip the system preamble/instructions" }, "userMessagePreprocessor": { "type": "object", "properties": { "dataSource": { "type": "string", "description": "Data source identifier for the preprocessor" }, "messageTemplate": { "type": "string", "description": "Template string applied to user messages before sending to the model" }, "maxResults": { "type": "number", "description": "Maximum number of results to include from the data source" }, "enabled": { "type": "boolean", "description": "Whether the preprocessor is active" }, "shouldInherit": { "type": "boolean", "description": "Whether child steps should inherit this preprocessor configuration" } }, "description": "Preprocessor applied to user messages before sending to the model" }, "preamble": { "type": "string", "description": "System preamble/instructions for the model" }, "multiModelEnabled": { "type": "boolean", "description": "Whether multi-model candidate generation is enabled" }, "editResponseEnabled": { "type": "boolean", "description": "Whether the user can edit the model's response" }, "config": { "type": "object", "properties": {}, "required": [], "description": "Additional model-specific configuration" } }, "required": ["model", "temperature", "maxResponseTokens"], "description": "Model settings override for HTML generation" }, "attachments": { "type": "array", "items": { "anyOf": [{ "type": "string" }, { "type": "object", "properties": { "url": { "type": "string" }, "filename": { "type": "string" }, "contentType": { "type": "string" } }, "required": ["url"] }] }, "description": "Files to attach: each entry is a URL string, or an object `{ url, filename?, contentType? }` to control the displayed filename and MIME type." }, "from": { "type": "string", "description": "Custom sender handle \u2014 only for apps with a custom domain or subdomain. Bare handle (`support`), full `support@your-domain.com`, or `Name <support@your-domain.com>`. Must resolve to a domain the app owns." }, "bodyType": { "enum": ["auto", "html", "markdown", "text"], "type": "string", "description": "How to interpret `body`: `auto` (default \u2014 detect HTML, else render markdown), `html` (send as-is), `markdown` (render to HTML), or `text` (send as plain text only, no HTML part). Every send includes a text/plain alternative." }, "text": { "type": "string", "description": "Explicit text/plain alternative body. Auto-derived from `body` if omitted." }, "replyTo": { "type": "string", "description": "Reply-To address for the email." }, "inReplyTo": { "type": "string", "description": "Message-ID this email replies to, for inbox threading (In-Reply-To header)." }, "references": { "type": "array", "items": { "type": "string" }, "description": "Prior Message-IDs in the thread, for inbox threading (References header)." }, "category": { "enum": ["transactional", "marketing"], "type": "string", "description": "`marketing` (default) adds one-click unsubscribe headers and skips recipients who unsubscribed from this app. Use `transactional` for receipts, alerts and password resets: no unsubscribe header, never suppressed, and cc/bcc preserved on a single message." }, "batchId": { "type": "string", "description": "Groups this send's per-recipient rows into one blast, so per-campaign stats are readable afterwards. Pass your own campaign id (1-64 printable chars) to join our delivery stats onto your own tables with no mapping; omit it and one is generated. Either way it comes back in the output." } }, "required": ["subject", "body"] },
4062
+ outputSchema: { "type": "object", "properties": { "recipients": { "type": "array", "items": { "type": "string" }, "description": "To addresses the message was sent to." }, "cc": { "type": "array", "items": { "type": "string" }, "description": "Cc addresses on the message (empty if none)." }, "bcc": { "type": "array", "items": { "type": "string" }, "description": "Bcc addresses on the message (empty if none)." }, "from": { "type": "string", "description": "The resolved sender address the message went out as (auto-selected when no `from` is given)." }, "suppressed": { "type": "array", "items": { "type": "string" }, "description": "Recipients skipped because they unsubscribed from this app. Always empty for a `transactional` send." }, "batchId": { "type": "string", "description": "Groups this send's per-recipient rows into one blast \u2014 the caller's `batchId` if supplied, otherwise a generated one. Returned unconditionally so an app can read per-blast stats back without having had to pass anything." } }, "required": ["recipients", "cc", "bcc", "from", "suppressed", "batchId"] }
4056
4063
  },
4057
4064
  "sendGmailDraft": {
4058
4065
  stepType: "sendGmailDraft",
@@ -6824,13 +6831,25 @@ var MindStudioAgent = class {
6824
6831
  );
6825
6832
  }
6826
6833
  /**
6827
- * Outbound email: the per-recipient delivery log, blast stats, and this app's
6828
- * unsubscribe list. Sending is the `sendEmail` action, not this namespace.
6834
+ * Outbound email: sending, the per-recipient delivery log, blast stats, and
6835
+ * this app's unsubscribe list.
6836
+ *
6837
+ * `email.send()` is the send path — the `sendEmail` action still works and runs
6838
+ * the same code, but `send()` returns a receipt including the `batchId` you read
6839
+ * stats back with. Note `category` defaults to `'marketing'`; see
6840
+ * {@link Email.send}.
6829
6841
  *
6830
6842
  * @example
6831
6843
  * ```ts
6832
6844
  * import { email } from '@mindstudio-ai/agent';
6833
6845
  *
6846
+ * await email.send({
6847
+ * to: 'a@b.com',
6848
+ * subject: 'Your receipt',
6849
+ * body: 'Thanks!',
6850
+ * category: 'transactional',
6851
+ * });
6852
+ *
6834
6853
  * // A support lookup: did this person get their mail?
6835
6854
  * const { messages } = await email.messages({ recipient: 'a@b.com' });
6836
6855
  *
@@ -7799,7 +7818,7 @@ var monacoSnippets = {
7799
7818
  "searchXPosts": { fields: [["query", "string"], ["scope", ["recent", "all"]], ["options", "object"]], outputKeys: ["posts"] },
7800
7819
  "searchYoutube": { fields: [["query", "string"], ["limitPages", "string"], ["filter", "string"], ["filterType", "string"]], outputKeys: ["results"] },
7801
7820
  "searchYoutubeTrends": { fields: [["bp", ["now", "music", "gaming", "films"]], ["hl", "string"], ["gl", "string"]], outputKeys: [] },
7802
- "sendEmail": { fields: [["subject", "string"], ["body", "string"]], outputKeys: ["recipients", "cc", "bcc", "from", "suppressed"] },
7821
+ "sendEmail": { fields: [["subject", "string"], ["body", "string"]], outputKeys: ["recipients", "cc", "bcc", "from", "suppressed", "batchId"] },
7803
7822
  "sendGmailDraft": { fields: [["draftId", "string"]], outputKeys: [] },
7804
7823
  "sendGmailMessage": { fields: [["to", "string"], ["subject", "string"], ["message", "string"], ["messageType", ["plain", "html", "markdown"]]], outputKeys: ["messageId"] },
7805
7824
  "sendSlackDirectMessage": { fields: [["slackUserId", "string"], ["messageType", ["string", "blocks"]], ["message", "string"]], outputKeys: [] },