@plexuslabs/simmail 0.1.0 → 0.1.1

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/index.d.ts CHANGED
@@ -1,33 +1,81 @@
1
- /** A message as the wait endpoints return it: with the codes and links pulled out. */
2
- export interface Email {
3
- address: string;
4
- folder: string;
5
- uid: number;
6
- message_id: string;
7
- in_reply_to?: string;
8
- from: string;
9
- from_name?: string;
10
- to: string[];
11
- subject: string;
12
- date: string;
13
- text: string;
14
- html?: string;
15
- links: string[];
16
- /** One-time codes found in the text. */
17
- codes: string[];
18
- /** Links that verify, confirm, reset or sign in. */
19
- magic_links: string[];
20
- /** Written by a simulated person. */
21
- by_persona?: boolean;
1
+ // Types for @plexuslabs/simmail. Objects use the API's own field names
2
+ // (snake_case); resource classes add camelCase getters and methods.
3
+
4
+ // ---------------------------------------------------------------- enums
5
+
6
+ import type { Temperament, Knows, Life, Length, Formality, MailClient, LinkHabit, SpamFilter, Access, Stance, BadKind, FailAt, Stranger, MailboxStatus, FaultKind, FaultStage, FaultScenario, MissingSite, LocalLinks, StreamKind, AiMode, EventType, JobKind, JobState, Folder, Protocol, Role, PersonaSource, StoryBy, EventAlias, ErrorCode } from "./enums.js";
7
+ export * from "./enums.js";
8
+
9
+ // ---------------------------------------------------------------- errors
10
+
11
+ /** A failed call. Catch it for every failure; the subclasses say which kind. */
12
+ export declare class SimmailError extends Error {
13
+ constructor(status: number, message: string, body?: unknown, requestId?: string);
14
+ /** The HTTP status. */
15
+ status: number;
16
+ /** The machine-readable error code (ErrorCode), when the server sends one. */
17
+ code?: ErrorCode | (string & {});
18
+ /** The request's id (X-Request-Id), to quote to support. */
19
+ requestId?: string;
20
+ /** The field that was wrong, when the server says. */
21
+ field?: string;
22
+ /** The values that would have been accepted, when the server says. */
23
+ allowed?: unknown[];
24
+ /** For a wait that timed out: what almost matched, and why each did not. */
25
+ nearMisses?: NearMiss[];
26
+ /** For a wait: what it looked for, in words. */
27
+ matched?: string;
28
+ waitedMs?: number;
29
+ /** The whole answer. */
30
+ body?: unknown;
31
+ }
32
+ export interface NearMiss { message_id: string; address: string; from: string; subject: string; received: string; failed: string[] }
33
+ /** 400: the request was not valid (bad_request, invalid_value, invalid_regex). */
34
+ export declare class ValidationError extends SimmailError {}
35
+ /** 401: no key, or a key that does not work. */
36
+ export declare class AuthError extends SimmailError {}
37
+ /** 402: your plan does not include this, or its limit is reached. */
38
+ export declare class PlanLimitError extends SimmailError {}
39
+ /** 403: your role or key may not do this. */
40
+ export declare class ForbiddenError extends SimmailError {}
41
+ /** 404: there is no such thing. */
42
+ export declare class NotFoundError extends SimmailError {}
43
+ /** 408: a wait ended with nothing matching. */
44
+ export declare class WaitTimeoutError extends SimmailError {
45
+ /** For a thread wait: the messages it had. */
46
+ messages?: Message[];
47
+ }
48
+ /** 409: a conflict, such as a person who decided not to reply. */
49
+ export declare class ConflictError extends SimmailError {}
50
+ /** 409: expectNoEmail saw an email. */
51
+ export declare class UnexpectedEmailError extends ConflictError {
52
+ /** The email that came. */
53
+ email?: Message;
54
+ }
55
+ /** 422: an Idempotency-Key was used before for a different request. */
56
+ export declare class IdempotencyError extends SimmailError {}
57
+ /** 429: too many requests. */
58
+ export declare class RateLimitedError extends SimmailError {
59
+ /** Seconds to wait, when the server says. */
60
+ retryAfter?: number;
22
61
  }
62
+ /** The error for a failed answer: by its code, else by its status. */
63
+ export declare function errorFor(status: number, message: string, body?: unknown, opts?: { retryAfter?: number; requestId?: string }): SimmailError;
23
64
 
24
- /** A message in a mailbox listing (text shortened) or read whole. */
25
- export interface Message {
26
- uid: number;
27
- folder: string;
28
- flags: string[];
29
- time: string;
30
- size: number;
65
+ // ---------------------------------------------------------------- data
66
+
67
+ export interface Attachment { filename: string; content_type: string; size?: number; content_id?: string; inline?: boolean }
68
+ /** An attachment to send: data is base64. */
69
+ export interface AttachmentInput { filename: string; content_type: string; data: string }
70
+
71
+ export interface MessageData {
72
+ /** The mailbox it is in (wait results; set for listed messages too). */
73
+ address?: string;
74
+ folder?: string;
75
+ uid?: number;
76
+ flags?: string[];
77
+ time?: string;
78
+ size?: number;
31
79
  message_id: string;
32
80
  in_reply_to?: string;
33
81
  references?: string[];
@@ -39,74 +87,552 @@ export interface Message {
39
87
  date: string;
40
88
  text: string;
41
89
  html?: string;
90
+ /** text, html or both: what the sender's client produced. */
91
+ format?: "text" | "html" | "both";
92
+ attachments?: Attachment[];
42
93
  headers?: Record<string, string>;
43
- attachments?: { filename: string; content_type: string; size?: number }[];
44
- format: string;
94
+ /** Wait results: every link, the one-time codes and the sign-in style links. */
95
+ links?: string[];
96
+ codes?: string[];
97
+ magic_links?: string[];
98
+ by_persona?: boolean;
99
+ }
100
+ /** @deprecated The 0.1.0 name of a wait result; it is a Message now. */
101
+ export type Email = Message;
102
+
103
+ export interface MailboxData { address: string; display_name?: string; can_login?: boolean; unread?: number; total?: number; persona_id?: string; persona?: string }
104
+ export interface FolderInfo { name: string; special_use?: string; total: number; unread: number }
105
+ export interface Thread { root: string; subject: string; last: string; messages: MessageData[] }
106
+
107
+ export interface SendInput {
108
+ from: string;
109
+ to: string[];
110
+ cc?: string[];
111
+ subject?: string;
112
+ text?: string;
113
+ html?: string;
114
+ attachments?: AttachmentInput[];
115
+ inReplyTo?: string;
116
+ in_reply_to?: string;
117
+ references?: string[];
118
+ forward?: { address: string; folder: string; uid: number };
45
119
  }
120
+ export interface SendResult extends AnswerFacts { status: string; message_id: string }
121
+ export interface InjectInput { to: string; folder?: Folder; from?: string; subject?: string; text?: string; raw?: string }
122
+ export interface WriteOptions {
123
+ /** The Idempotency-Key header. Default: a new key for each call; false sends none. */
124
+ idempotencyKey?: string | false;
125
+ }
126
+ /** Facts about an answer, beside its data (not enumerable, not in JSON). */
127
+ export interface AnswerFacts {
128
+ /** The request's id (X-Request-Id). */
129
+ readonly requestId?: string;
130
+ /** True when the server had already done this (a repeated Idempotency-Key). */
131
+ readonly replayed?: boolean;
132
+ }
133
+
134
+ export interface AccountData { address: string; display_name?: string; password?: string; username?: string; allow_smtp?: boolean; allow_imap?: boolean; disabled?: boolean; note?: string; messages?: number }
135
+ export interface AccountInput { display_name?: string; username?: string; allow_smtp?: boolean; allow_imap?: boolean; note?: string; disabled?: boolean; password?: string; clear_password?: boolean }
136
+ export interface LoginTest { smtp?: { ok: boolean; address: string }; imap?: { ok: boolean; address: string } }
137
+
138
+ export interface PersonaInput {
139
+ name?: string;
140
+ addresses?: string[];
141
+ role?: string;
142
+ org?: string;
143
+ who?: string;
144
+ wants?: string;
145
+ character?: string;
146
+ knows?: Knows;
147
+ temperament?: Temperament;
148
+ case_ids?: string[];
149
+ style?: { length?: Length; formality?: Formality; typos?: number; signature?: string; client?: MailClient; links?: LinkHabit; filter?: SpamFilter; access?: Access };
150
+ timing?: {
151
+ reply_rate?: number;
152
+ delay?: { median?: string; spread?: number };
153
+ hours?: { tz?: string; days?: string; from?: string; to?: string };
154
+ ooo?: { from: string; to: string; message?: string }[];
155
+ };
156
+ behaviors?: { double_reply?: number; bounce?: number; cc_someone?: number; forward_to?: string; keep_promises?: number };
157
+ external_ids?: Record<string, string>;
158
+ paused?: boolean;
159
+ }
160
+ export interface PersonaData extends PersonaInput { id: string; name: string; addresses: string[]; provider?: PersonaSource; created?: string }
161
+ export interface TemperamentInfo { name: Temperament; weight: number; reply_rate: number; median_delay: string; text: string }
162
+
163
+ export interface CasePerson { ref?: string; name?: string; role?: string; org?: string; addresses?: string[]; notes?: string[]; persona_id?: string; external_ids?: Record<string, string>; who?: string; wants?: string; knows?: Knows; temperament?: Temperament; character?: string }
164
+ export interface CaseInput { title?: string; kind?: string; facts?: Record<string, unknown>; known?: Record<string, unknown>; people?: CasePerson[]; notes?: string[]; directions?: string }
165
+ export interface CaseData extends CaseInput { id: string; story?: string; amendments?: string[]; story_by?: StoryBy; story_written_by?: StoryBy; created?: string; updated?: string }
166
+
167
+ export interface ClockState { now: string; speed: number; frozen: boolean; real: boolean }
168
+ export interface AdvanceResult { clock: ClockState; jobs_run: number }
169
+ export interface JobData { id: number; state: JobState; due: string; ran?: string; created?: string; kind: JobKind; persona_id?: string; persona?: string; case_id?: string; to?: string[]; plan?: string; note?: string; inbound?: { subject: string; from: string; text: string } }
170
+ export interface JobList { now: string; jobs: Job[] }
46
171
 
47
- export interface WaitForEmail {
48
- to: string;
172
+ export interface SimEventData { id: number; at: string; kind: EventType; data: Record<string, unknown> }
173
+ export interface RunDueResult { ran: { id: number; kind: JobKind; persona?: string; state: JobState | "error"; note?: string }[]; count: number; remaining: number }
174
+ export interface TestAddressData { address: string; tag: string }
175
+ export interface WaitMany { messages: Message[]; count: number; waited_ms: number; matched: string }
176
+ export interface NoEmail { none: true; waited_ms: number; matched: string }
177
+ export interface ThreadData { thread: string; count: number; quiet_ms: number; waited_ms: number }
178
+
179
+ export interface FaultRule {
180
+ name?: string;
181
+ kind?: FaultKind;
182
+ stage: FaultStage;
183
+ match?: string;
184
+ rate?: number;
185
+ code?: number;
186
+ enhanced?: string;
187
+ message?: string;
188
+ delay_ms?: number;
189
+ retry_after?: number;
190
+ per_minute?: number;
191
+ window?: { days: string; from: string; to: string; tz?: string };
192
+ enabled?: boolean;
193
+ scenario?: FaultScenario;
194
+ }
195
+ export interface FaultScenarioInfo { id: FaultScenario; provider: string; name: string; description: string; rule: FaultRule }
196
+
197
+ export interface RecipientRule { create?: boolean; bad_rate?: number; bad_kinds?: BadKind[]; fail_at?: FailAt; stranger?: Stranger; silent?: boolean }
198
+ export interface RecipientRules { default?: RecipientRule; domains?: Record<string, RecipientRule>; status?: Record<string, MailboxStatus>; seed?: string }
199
+
200
+ export interface LinkPolicy { enabled: boolean; allow_private: boolean; allow?: string[]; deny?: string[]; max_links: number; unsubscribe: boolean; user_agent?: string; sender_check: number; missing_site: MissingSite; local_links: LocalLinks }
201
+ export interface JunkSettings { enabled: boolean; strict: number; normal: number; lenient: number; strict_domains: string[]; ignore: string[]; count_network: boolean }
202
+
203
+ export interface Webhook { name?: string; url: string; events: EventType[]; secret?: string }
204
+
205
+ export interface Stream { id?: string; name: string; kind: StreamKind; to: string[]; per_day: number; business_hours?: boolean; brief?: string; new_people?: number; enabled?: boolean; sent?: number; next?: string }
206
+ export interface World { company: { name: string; domain: string; tz?: string; mailboxes?: unknown[] }; streams: Stream[] }
207
+
208
+ export interface HealthReport { message_id: string; subject?: string; from?: string; at?: string; spam?: Record<string, unknown>; links?: unknown[]; links_done?: boolean; broken?: number; local?: number; dangerous?: number; unsubscribe?: boolean; compat?: unknown; access?: unknown; locked?: boolean }
209
+ export interface Extracted { codes: string[]; links: string[]; magic_links: string[] }
210
+
211
+ /** The filters of every mail wait. */
212
+ export interface MailFilters {
213
+ /** An address, *@domain, or a list of them (or use tag). */
214
+ to?: string | string[];
215
+ /** The addresses made with this tag (testAddress). */
216
+ tag?: string;
49
217
  from?: string;
50
218
  subject?: string;
51
219
  contains?: string;
52
- folder?: string;
53
- /** Also match messages already there. */
220
+ subjectRegex?: RegExp | string;
221
+ bodyRegex?: RegExp | string;
222
+ /** {Name: value} or ["Name:value"]. */
223
+ header?: Record<string, string> | string[];
224
+ hasAttachment?: boolean;
225
+ attachmentName?: string;
226
+ linkDomain?: string;
227
+ folder?: Folder;
228
+ /** Only mail after this: a Date, RFC 3339 or a message id. */
229
+ since?: Date | string;
230
+ /** Message ids to skip. */
231
+ exclude?: string | string[];
232
+ /** Also mail that came before the wait. */
54
233
  existing?: boolean;
55
- /** Milliseconds, or a duration like "45s". Default 30s; at most 5m. */
234
+ /** Newer filters are passed to the server as they are (camelCase becomes snake_case). */
235
+ [more: string]: unknown;
236
+ }
237
+ export interface WaitOptions extends MailFilters {
238
+ /** Wait for this many (1 to 100); the answer is then {messages, count, …}. */
239
+ count?: number;
240
+ /** Milliseconds, or a duration such as "45s" (at most 5 minutes). */
56
241
  timeout?: number | string;
57
242
  }
58
-
59
- export interface WaitForReply {
60
- /** The person's address. */
243
+ export interface NoEmailOptions extends MailFilters {
244
+ /** How long nothing may arrive: milliseconds or a duration. Default "5s". */
245
+ within?: number | string;
246
+ }
247
+ export interface EventWaitOptions {
248
+ /** An EventType, EventAlias or prefix such as "persona.*", or a list. */
249
+ type: EventType | EventAlias | (EventType | EventAlias)[];
250
+ /** A persona id or address. */
251
+ persona?: string;
252
+ messageId?: string;
253
+ /** An event id: only events after it. */
254
+ after?: number;
255
+ /** Has the persona act now on what is due. */
256
+ fast?: boolean;
257
+ timeout?: number | string;
258
+ }
259
+ export interface ThreadWaitOptions {
260
+ messageId?: string;
261
+ thread?: string;
262
+ /** How long nothing new may join it. Default "10s". */
263
+ quiet?: number | string;
264
+ fast?: boolean;
265
+ /** Default 60s. */
266
+ timeout?: number | string;
267
+ }
268
+ export interface JobFilters {
269
+ state?: JobState;
270
+ /** A persona id or address. */
271
+ persona?: string;
272
+ kind?: JobKind;
273
+ /** A Date, RFC 3339 or a duration from the simulated now, such as "1h". */
274
+ dueBefore?: Date | string;
275
+ limit?: number;
276
+ [more: string]: unknown;
277
+ }
278
+ export interface RunDueOptions { persona?: string; kind?: JobKind; before?: Date | string; limit?: number }
279
+ export interface ReplyWaitOptions {
61
280
  from: string;
62
- /** Only a reply to this Message-ID. */
63
281
  inReplyTo?: string;
64
- /** Have them write their due reply now instead of on the simulated clock. Default true. */
65
282
  fast?: boolean;
66
283
  timeout?: number | string;
284
+ [more: string]: unknown;
67
285
  }
68
286
 
69
- export interface SendEmail {
70
- from: string;
71
- to: string[];
72
- cc?: string[];
73
- subject: string;
74
- text?: string;
75
- html?: string;
76
- /** Thread this onto a conversation. */
77
- inReplyTo?: string;
78
- references?: string[];
79
- attachments?: { filename: string; content_type: string; /** base64 */ data: string }[];
287
+ // ---------------------------------------------------------------- resources
288
+
289
+ /** Every resource keeps the API's fields, plus .raw (the answer as it came). */
290
+ export declare class Resource {
291
+ readonly raw: Record<string, unknown>;
292
+ readonly client: Simmail;
293
+ toJSON(): Record<string, unknown>;
80
294
  }
81
295
 
82
- export class SimmailError extends Error {
83
- status: number;
296
+ export declare class Message extends Resource {}
297
+ export interface Message extends MessageData {
298
+ readonly messageId: string;
299
+ readonly inReplyTo?: string;
300
+ readonly fromName?: string;
301
+ readonly codes: string[];
302
+ readonly magicLinks: string[];
303
+ readonly byPersona: boolean;
304
+ readonly isRead: boolean;
305
+ /** Set by full() and message(). */
306
+ readonly safe?: { html: string; remote_blocked: number };
307
+ /** For a persona's mailbox: the case the thread is linked to. */
308
+ readonly case?: { id: string; how: string };
309
+ full(opts?: { remote?: boolean; theme?: "light" }): Promise<Message>;
310
+ source(): Promise<string>;
311
+ attachment(n: number): Promise<Uint8Array>;
312
+ extract(): Promise<Extracted>;
313
+ markRead(): Promise<unknown>;
314
+ markUnread(): Promise<unknown>;
315
+ move(folder: Folder): Promise<unknown>;
316
+ reply(opts?: { from?: string; text?: string; html?: string; cc?: string[]; attachments?: AttachmentInput[] }): Promise<SendResult>;
317
+ waitForReply(opts?: Partial<ReplyWaitOptions>): Promise<Message>;
318
+ health(): Promise<HealthReport>;
319
+ /** For wait results: how long the wait took, and what it looked for. */
320
+ readonly waitedMs?: number;
321
+ readonly matched?: string;
322
+ /** Its conversation, once nothing new has joined it for opts.quiet. */
323
+ waitUntilQuiet(opts?: Omit<ThreadWaitOptions, "messageId" | "thread">): Promise<Conversation>;
324
+ }
325
+
326
+ export declare class Mailbox extends Resource {}
327
+ export interface Mailbox extends MailboxData {
328
+ readonly personaId?: string;
329
+ readonly displayName?: string;
330
+ readonly canLogin?: boolean;
331
+ messages(opts?: { folder?: Folder }): AsyncGenerator<Message>;
332
+ list(opts?: { folder?: Folder }): Promise<Message[]>;
333
+ waitForEmail(opts: Omit<WaitOptions, "to"> & { count: number }): Promise<WaitMany>;
334
+ waitForEmail(opts?: Omit<WaitOptions, "to">): Promise<Message>;
335
+ folders(): Promise<FolderInfo[]>;
336
+ threads(): Promise<Thread[]>;
337
+ setStatus(status: MailboxStatus): Promise<unknown>;
338
+ send(msg: Omit<SendInput, "from">, opts?: WriteOptions): Promise<SendResult>;
339
+ inject(msg: Omit<InjectInput, "to">, opts?: WriteOptions): Promise<unknown>;
340
+ signature(): Promise<{ signature: string }>;
341
+ setSignature(signature: string): Promise<unknown>;
342
+ getPersona(): Promise<Persona | null>;
84
343
  }
85
344
 
86
- export class Simmail {
87
- /** Defaults: SIMMAIL_URL (such as https://c1.simmail.dev), SIMMAIL_API_KEY, SIMMAIL_UNIVERSE. */
88
- constructor(opts?: { baseUrl?: string; apiKey?: string; universe?: string });
345
+ export declare class Persona extends Resource {}
346
+ export interface Persona extends PersonaData {
347
+ readonly address: string;
348
+ readonly caseIds: string[];
349
+ readonly externalIds: Record<string, string>;
350
+ refresh(): Promise<Persona>;
351
+ update(changes: PersonaInput): Promise<Persona>;
352
+ delete(): Promise<unknown>;
353
+ pause(): Promise<Persona>;
354
+ resume(): Promise<Persona>;
355
+ mailbox(): Mailbox;
356
+ waitForReply(opts?: Partial<ReplyWaitOptions>): Promise<Message>;
357
+ memory(): Promise<Record<string, string[]>>;
358
+ remember(text: string, opts?: { caseId?: string; source?: string }): Promise<unknown>;
359
+ forgetMemory(opts?: { caseId?: string }): Promise<unknown>;
360
+ relations(): Promise<{ relations: unknown[]; stances: unknown[] }>;
361
+ forget(address?: string): Promise<unknown>;
362
+ jobs(opts?: Omit<JobFilters, "persona">): Promise<Job[]>;
363
+ runDue(opts?: Omit<RunDueOptions, "persona">): Promise<RunDueResult>;
364
+ /** Waits until they do something ("replied", "declined", "clicked", an EventType…). */
365
+ waitFor(type: EventWaitOptions["type"], opts?: Omit<EventWaitOptions, "type" | "persona">): Promise<SimEvent>;
366
+ }
367
+
368
+ export declare class Case extends Resource {}
369
+ export interface Case extends CaseData {
370
+ readonly people: CasePerson[];
371
+ readonly storyBy?: StoryBy;
372
+ refresh(opts?: { redact?: boolean }): Promise<Case>;
373
+ push(update: CaseInput): Promise<Case>;
374
+ addPeople(people: CasePerson[]): Promise<Case>;
375
+ initiate(opts: { persona: string; to: string | string[]; intent?: string; after?: string }): Promise<{ job_id: number }>;
376
+ regenerate(): Promise<Case>;
377
+ editStory(edit: { story?: string; amendments?: string[]; directions?: string }): Promise<Case>;
378
+ delete(): Promise<unknown>;
379
+ }
380
+
381
+ export declare class Job extends Resource {}
382
+ export interface Job extends JobData {
383
+ readonly personaId?: string;
384
+ readonly caseId?: string;
385
+ readonly isPending: boolean;
386
+ run(): Promise<unknown>;
387
+ skip(): Promise<unknown>;
388
+ bounce(): Promise<unknown>;
389
+ reschedule(when: { due?: string; in?: string }): Promise<unknown>;
390
+ }
391
+
392
+ export declare class Account extends Resource {}
393
+ export interface Account extends AccountData {
394
+ readonly displayName?: string;
395
+ readonly allowSmtp?: boolean;
396
+ readonly allowImap?: boolean;
397
+ update(changes: AccountInput): Promise<unknown>;
398
+ delete(): Promise<unknown>;
399
+ test(opts?: { password?: string; username?: string; proto?: Protocol }): Promise<LoginTest>;
400
+ mailbox(): Mailbox;
401
+ }
402
+
403
+ /** Something that happened. kind is an EventType. */
404
+ export declare class SimEvent extends Resource {}
405
+ export interface SimEvent extends SimEventData {
406
+ readonly personaId?: string;
407
+ readonly messageId?: string;
408
+ readonly caseId?: string;
409
+ /** For waitForEvent: how long it took. */
410
+ readonly waitedMs?: number;
411
+ persona(): Promise<Persona | null>;
412
+ }
413
+
414
+ /** A conversation, its messages oldest first. */
415
+ export declare class Conversation extends Resource {}
416
+ export interface Conversation extends ThreadData {
417
+ messages: Message[];
418
+ readonly root: string;
419
+ readonly latest: Message;
420
+ readonly quietMs: number;
421
+ readonly waitedMs: number;
422
+ waitUntilQuiet(opts?: Omit<ThreadWaitOptions, "messageId" | "thread">): Promise<Conversation>;
423
+ reply(opts?: { from?: string; text?: string; html?: string; cc?: string[]; attachments?: AttachmentInput[] }): Promise<SendResult>;
424
+ }
425
+
426
+ /** A fresh address for one test. */
427
+ export declare class TestAddress extends Mailbox {}
428
+ export interface TestAddress extends TestAddressData {
429
+ expectNoEmail(opts?: Omit<NoEmailOptions, "to" | "tag">): Promise<NoEmail>;
430
+ cleanup(opts?: { folder?: Folder }): Promise<{ deleted: number; addresses: string[] }>;
431
+ }
432
+
433
+ /** Every address made with one tag. */
434
+ export declare class TagGroup {
435
+ readonly client: Simmail;
436
+ readonly tag: string;
437
+ newAddress(opts?: { domain?: string; prefix?: string }): Promise<TestAddress>;
438
+ waitForEmail(opts: Omit<WaitOptions, "tag"> & { count: number }): Promise<WaitMany>;
439
+ waitForEmail(opts?: Omit<WaitOptions, "tag">): Promise<Message>;
440
+ expectNoEmail(opts?: Omit<NoEmailOptions, "tag">): Promise<NoEmail>;
441
+ messages(opts?: { folder?: Folder; limit?: number }): Promise<Message[]>;
442
+ cleanup(opts?: { folder?: Folder }): Promise<{ deleted: number; addresses: string[] }>;
443
+ }
444
+
445
+ // ---------------------------------------------------------------- namespaces
446
+
447
+ export interface MailboxesApi { (): Promise<Mailbox[]>; list(): Promise<Mailbox[]>; get(address: string): Mailbox }
448
+ export interface PersonasApi {
449
+ (): Promise<Persona[]>;
450
+ list(): Promise<Persona[]>;
451
+ get(id: string): Promise<Persona>;
452
+ create(p: PersonaInput): Promise<Persona>;
453
+ update(id: string, changes: PersonaInput): Promise<Persona>;
454
+ delete(id: string): Promise<unknown>;
455
+ fromMail(address: string, opts?: { paused?: boolean }): Promise<{ persona: Persona; by: string; people: number }>;
456
+ temperaments(): Promise<TemperamentInfo[]>;
457
+ byAddress(address: string): Promise<Persona | null>;
458
+ }
459
+ export interface CasesApi { (): Promise<Case[]>; list(): Promise<Case[]>; get(id: string, opts?: { redact?: boolean }): Promise<Case>; push(id: string, data: CaseInput): Promise<Case>; delete(id: string): Promise<unknown> }
460
+ export interface ClockApi { (): Promise<ClockState>; get(): Promise<ClockState>; advance(duration: string): Promise<AdvanceResult>; freeze(): Promise<ClockState>; speed(factor: number): Promise<ClockState>; live(): Promise<ClockState> }
461
+ export interface JobsApi {
462
+ (opts?: JobFilters): Promise<JobList>;
463
+ list(opts?: JobFilters): Promise<Job[]>;
464
+ run(id: number): Promise<unknown>;
465
+ skip(id: number): Promise<unknown>;
466
+ bounce(id: number): Promise<unknown>;
467
+ reschedule(id: number, when: { due?: string; in?: string }): Promise<unknown>;
468
+ /** Runs the matching pending jobs now, in due order. */
469
+ runDue(opts?: RunDueOptions): Promise<RunDueResult>;
470
+ }
471
+ export interface WorldApi { (): Promise<{ world: World; kinds: StreamKind[] }>; get(): Promise<{ world: World; kinds: StreamKind[] }>; set(world: World): Promise<{ world: World }>; sendNow(streamId: string): Promise<{ status: string; note: string }> }
472
+ export interface FaultsApi { (): Promise<FaultRule[]>; list(): Promise<FaultRule[]>; set(rules: FaultRule[]): Promise<FaultRule[]>; clear(): Promise<FaultRule[]>; scenarios(): Promise<FaultScenarioInfo[]>; add(scenario: FaultScenario, opts?: { match?: string; window?: FaultRule["window"] }): Promise<FaultRule[]> }
473
+ export interface RecipientsApi { (): Promise<RecipientRules>; get(): Promise<RecipientRules>; set(rules: RecipientRules): Promise<RecipientRules>; setStatus(address: string, status: MailboxStatus): Promise<unknown> }
474
+ export interface WebhooksApi { (): Promise<Webhook[]>; list(): Promise<Webhook[]>; set(hooks: Webhook[]): Promise<Webhook[]>; clear(): Promise<Webhook[]> }
475
+ export interface InsightsApi { (opts?: { days?: number }): Promise<Record<string, unknown>>; get(opts?: { days?: number }): Promise<Record<string, unknown>>; shares(): Promise<{ token: string; title: string; days: number; created: string }[]>; share(opts: { title?: string; days?: number }): Promise<{ token: string; path: string }>; revoke(token: string): Promise<unknown> }
476
+ export interface EventsApi { (opts?: { since?: number; signal?: AbortSignal }): AsyncGenerator<SimEvent>; stream(opts?: { since?: number; signal?: AbortSignal }): AsyncGenerator<SimEvent>; log(opts?: { since?: number; limit?: number }): Promise<SimEvent[]>; waitFor(opts: EventWaitOptions): Promise<SimEvent> }
477
+ export interface AccountsApi { (): Promise<Account[]>; list(): Promise<Account[]>; create(address: string, password: string, extra?: AccountInput): Promise<unknown>; update(address: string, changes: AccountInput): Promise<unknown>; delete(address: string): Promise<unknown>; test(address: string, opts?: { password?: string; username?: string; proto?: Protocol }): Promise<LoginTest> }
478
+ export interface InspectApi { health(messageId: string): Promise<HealthReport>; extract(input: { text?: string; html?: string }): Promise<Extracted>; schema(): Promise<Schema> }
479
+ export interface SchemaOption { value: string; label?: string; description?: string; feature?: string }
480
+ export interface SchemaField { type: string; description?: string; values?: SchemaOption[]; default?: unknown; min?: number; max?: number; feature?: string; unset?: string }
481
+ export interface Schema { version: number; areas: Record<string, { endpoints?: string[]; description?: string; feature?: string; fields: Record<string, SchemaField> }> }
482
+ export interface BehaviourApi {
483
+ ai(): Promise<{ mode: AiMode }>; setAi(mode: AiMode): Promise<{ mode: AiMode }>; aiBudget(): Promise<Record<string, unknown>>;
484
+ pace(): Promise<{ factor: number; ignore_hours: boolean }>; setPace(opts: { factor?: number; ignoreHours?: boolean }): Promise<unknown>;
485
+ links(): Promise<{ policy: LinkPolicy; habits: unknown[]; default_user_agent: string }>; setLinks(policy: LinkPolicy): Promise<unknown>;
486
+ retention(): Promise<{ mail_days: number; event_days: number }>; setRetention(opts: { mailDays?: number; eventDays?: number }): Promise<unknown>; prune(): Promise<{ messages: number; events: number; jobs: number }>;
487
+ junk(): Promise<{ settings: JunkSettings; defaults: JunkSettings }>; setJunk(settings: JunkSettings): Promise<JunkSettings>;
488
+ }
489
+
490
+ // ---------------------------------------------------------------- client
491
+
492
+ export interface SimmailOptions {
493
+ /** The server, such as https://c1.simmail.dev. Default: SIMMAIL_URL. */
494
+ baseUrl?: string;
495
+ /** An API key, smk_…. Default: SIMMAIL_API_KEY. */
496
+ apiKey?: string;
497
+ /** A universe id; only needed when the key can reach more than one. Default: SIMMAIL_UNIVERSE. */
498
+ universe?: string;
499
+ /** Headers sent with every request. */
500
+ headers?: Record<string, string>;
501
+ /** How often a GET, or a write with an Idempotency-Key, is tried again after a network error, 502 or 503. Default 2. */
502
+ retries?: number;
503
+ }
504
+
505
+ export declare class Simmail {
506
+ constructor(opts?: SimmailOptions);
89
507
  baseUrl: string;
90
- request<T = unknown>(method: string, path: string, body?: unknown, query?: Record<string, unknown>): Promise<T>;
508
+ apiKey: string;
509
+ universe: string;
510
+ headers: Record<string, string>;
511
+ retries: number;
512
+ /** A client like this one that sends X-Request-Id: id with every request. */
513
+ withRequestId(id: string): Simmail;
514
+ /** Every setting's allowed values, defaults and limits. */
515
+ schema(): Promise<Schema>;
516
+
517
+ // namespaces (each also callable as the 0.1.0 method of the same name)
518
+ mailboxes: MailboxesApi;
519
+ personas: PersonasApi;
520
+ cases: CasesApi;
521
+ clock: ClockApi;
522
+ jobs: JobsApi;
523
+ world: WorldApi;
524
+ faults: FaultsApi;
525
+ recipients: RecipientsApi;
526
+ webhooks: WebhooksApi;
527
+ insights: InsightsApi;
528
+ events: EventsApi;
529
+ accounts: AccountsApi;
530
+ inspect: InspectApi;
531
+ behaviour: BehaviourApi;
532
+
533
+ /** Any API call. */
534
+ request<T = any>(method: string, path: string, body?: unknown, query?: Record<string, unknown>, opts?: { headers?: Record<string, string> }): Promise<T>;
535
+ /** Any call whose answer is not JSON. */
536
+ requestRaw(method: string, path: string, query?: Record<string, unknown>, opts?: { asBytes?: false }): Promise<string>;
537
+ requestRaw(method: string, path: string, query: Record<string, unknown> | undefined, opts: { asBytes: true }): Promise<Uint8Array>;
538
+
91
539
  me(): Promise<Record<string, unknown>>;
92
- waitForEmail(q: WaitForEmail): Promise<Email>;
93
- waitForReply(q: WaitForReply): Promise<Email>;
94
- extract(x: { text?: string; html?: string }): Promise<{ codes: string[]; links: string[]; magic_links: string[] }>;
95
- mailboxes(): Promise<unknown[]>;
96
- messages(address: string, opts?: { folder?: string }): Promise<Message[]>;
97
- message(address: string, folder: string, uid: number): Promise<Message>;
98
- health(messageId: string): Promise<Record<string, unknown>>;
99
- send(msg: SendEmail): Promise<{ message_id: string; status: string }>;
100
- accounts(): Promise<unknown[]>;
101
- createAccount(address: string, password: string): Promise<unknown>;
102
- personas(): Promise<unknown[]>;
103
- createPersona(p: Record<string, unknown>): Promise<unknown>;
104
- pushCase(id: string, kase: Record<string, unknown>): Promise<unknown>;
105
- clock(): Promise<unknown>;
106
- advance(duration: string): Promise<unknown>;
107
- freeze(): Promise<unknown>;
108
- speed(factor: number): Promise<unknown>;
109
- live(): Promise<unknown>;
540
+ universes(): Promise<{ id: string; name: string; org: string; created: string; role: Role }[]>;
541
+ status(): Promise<Record<string, unknown>>;
542
+ connection(): Promise<Record<string, unknown>>;
543
+ connectionCert(): Promise<string>;
544
+
545
+ mailbox(address: string): Mailbox;
546
+ waitForEmail(opts: WaitOptions & { count: number }): Promise<WaitMany>;
547
+ waitForEmail(opts: WaitOptions): Promise<Message>;
548
+ /** Passes when no matching email arrives within opts.within; throws UnexpectedEmailError when one does. */
549
+ expectNoEmail(opts: NoEmailOptions): Promise<NoEmail>;
550
+ waitForReply(opts: ReplyWaitOptions): Promise<Message>;
551
+ waitForEvent(opts: EventWaitOptions): Promise<SimEvent>;
552
+ waitForThread(opts: ThreadWaitOptions): Promise<Conversation>;
553
+
554
+ createAddress(opts?: { domain?: string; prefix?: string; tag?: string }): Promise<TestAddressData>;
555
+ testAddress(opts?: { domain?: string; prefix?: string; tag?: string }): Promise<TestAddress>;
556
+ tag(tag: string): TagGroup;
557
+ listMessages(opts: { tag?: string; to?: string | string[]; folder?: Folder; limit?: number }): Promise<Message[]>;
558
+ deleteMessages(opts: { tag?: string; to?: string | string[]; folder?: Folder }): Promise<{ deleted: number; addresses: string[] }>;
559
+ extract(input: { text?: string; html?: string }): Promise<Extracted>;
560
+ folders(address: string): Promise<FolderInfo[]>;
561
+ messages(address: string, opts?: { folder?: Folder }): Promise<Message[]>;
562
+ message(address: string, folder: Folder, uid: number, opts?: { remote?: boolean; theme?: "light" }): Promise<{ message: Message; safe?: { html: string; remote_blocked: number }; case?: { id: string; how: string } }>;
563
+ rawMessage(address: string, folder: Folder, uid: number): Promise<string>;
564
+ attachment(address: string, folder: Folder, uid: number, n: number): Promise<Uint8Array>;
565
+ updateMessage(address: string, folder: Folder, uid: number, change: { seen?: boolean; moveTo?: Folder }): Promise<unknown>;
566
+ threads(address: string): Promise<Thread[]>;
567
+ setMailboxStatus(address: string, status: MailboxStatus): Promise<unknown>;
568
+ send(msg: SendInput, opts?: WriteOptions): Promise<SendResult>;
569
+ inject(msg: InjectInput, opts?: WriteOptions): Promise<AnswerFacts & Record<string, unknown>>;
570
+ signature(address: string): Promise<{ signature: string }>;
571
+ setSignature(address: string, signature: string): Promise<unknown>;
572
+ rescue(messageId: string, address: string): Promise<unknown>;
573
+ retry(messageId: string, address?: string): Promise<{ results: unknown }>;
574
+ health(messageId: string): Promise<HealthReport>;
575
+
576
+ createAccount(address: string, password: string, extra?: AccountInput): Promise<unknown>;
577
+ updateAccount(address: string, changes: AccountInput): Promise<unknown>;
578
+ deleteAccount(address: string): Promise<unknown>;
579
+ testAccount(address: string, opts?: { password?: string; username?: string; proto?: Protocol }): Promise<LoginTest>;
580
+
581
+ persona(id: string): Promise<Persona>;
582
+ createPersona(p: PersonaInput): Promise<Persona>;
583
+ updatePersona(id: string, changes: PersonaInput): Promise<Persona>;
584
+ deletePersona(id: string): Promise<unknown>;
585
+ personaFromMail(address: string, opts?: { paused?: boolean }): Promise<{ persona: Persona; by: string; people: number }>;
586
+ temperaments(): Promise<TemperamentInfo[]>;
587
+ memory(id: string): Promise<Record<string, string[]>>;
588
+ addMemory(id: string, note: { text: string; caseId?: string; source?: string }): Promise<unknown>;
589
+ clearMemory(id: string, opts?: { caseId?: string }): Promise<unknown>;
590
+ addMemoryByExternal(note: { system: string; id: string; text: string; caseId?: string; source?: string }): Promise<unknown>;
591
+ relations(id: string): Promise<{ relations: unknown[]; stances: unknown[] }>;
592
+ forgetRelations(id: string, opts?: { address?: string }): Promise<unknown>;
593
+
594
+ getCase(id: string, opts?: { redact?: boolean }): Promise<Case>;
595
+ pushCase(id: string, data: CaseInput): Promise<Case>;
596
+ addCasePeople(id: string, people: CasePerson[]): Promise<Case>;
597
+ initiateCase(id: string, opts: { persona: string; to: string | string[]; intent?: string; after?: string }): Promise<{ job_id: number }>;
598
+ regenerateCase(id: string): Promise<Case>;
599
+ editCaseStory(id: string, edit: { story?: string; amendments?: string[]; directions?: string }): Promise<Case>;
600
+ deleteCase(id: string): Promise<unknown>;
601
+
602
+ advance(duration: string): Promise<AdvanceResult>;
603
+ freeze(): Promise<ClockState>;
604
+ speed(factor: number): Promise<ClockState>;
605
+ live(): Promise<ClockState>;
606
+ runJob(id: number): Promise<unknown>;
607
+ skipJob(id: number): Promise<unknown>;
608
+ bounceJob(id: number): Promise<unknown>;
609
+ rescheduleJob(id: number, when: { due?: string; in?: string }): Promise<unknown>;
610
+ log(opts?: { since?: number; limit?: number }): Promise<SimEvent[]>;
611
+ /** Runs the matching pending jobs now, in due order. */
612
+ runDue(opts?: RunDueOptions): Promise<RunDueResult>;
613
+
614
+ ai(): Promise<{ mode: AiMode }>;
615
+ setAi(mode: AiMode): Promise<{ mode: AiMode }>;
616
+ aiBudget(): Promise<Record<string, unknown>>;
617
+ pace(): Promise<{ factor: number; ignore_hours: boolean }>;
618
+ setPace(opts: { factor?: number; ignoreHours?: boolean }): Promise<unknown>;
619
+ setRecipients(rules: RecipientRules): Promise<RecipientRules>;
620
+ links(): Promise<{ policy: LinkPolicy; habits: unknown[]; default_user_agent: string }>;
621
+ setLinks(policy: LinkPolicy): Promise<unknown>;
622
+ retention(): Promise<{ mail_days: number; event_days: number }>;
623
+ setRetention(opts: { mailDays?: number; eventDays?: number }): Promise<unknown>;
624
+ pruneRetention(): Promise<{ messages: number; events: number; jobs: number }>;
625
+ junkSettings(): Promise<{ settings: JunkSettings; defaults: JunkSettings }>;
626
+ setJunkSettings(settings: JunkSettings): Promise<JunkSettings>;
627
+ setFaults(rules: FaultRule[]): Promise<FaultRule[]>;
628
+ faultScenarios(): Promise<FaultScenarioInfo[]>;
629
+ addFaultScenario(id: FaultScenario, opts?: { match?: string; window?: FaultRule["window"] }): Promise<FaultRule[]>;
630
+ insightShares(): Promise<{ token: string; title: string; days: number; created: string }[]>;
631
+ shareInsights(opts: { title?: string; days?: number }): Promise<{ token: string; path: string }>;
632
+ revokeInsightShare(token: string): Promise<unknown>;
633
+ setWebhooks(hooks: Webhook[]): Promise<Webhook[]>;
634
+ setWorld(world: World): Promise<{ world: World }>;
635
+ sendFromStream(id: string): Promise<{ status: string; note: string }>;
110
636
  }
111
637
 
112
638
  export default Simmail;