groundmail 0.15.1 → 0.17.0

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/README.md CHANGED
@@ -25,6 +25,27 @@ const email = await tx.emails.send({
25
25
  console.log(email.id, email.status)
26
26
  ```
27
27
 
28
+ ## Testing
29
+
30
+ Use a test key (`gm_test_…`, create one with `tx.apiKeys.create({ test: true })` or in the
31
+ dashboard) to run sends through validation, templates and webhooks without delivering anything.
32
+ Simulator addresses produce a specific outcome with any key and never leave Groundmail:
33
+
34
+ ```ts
35
+ import { simulatorAddress } from 'groundmail'
36
+
37
+ await tx.emails.send({
38
+ from: 'ops@yourdomain.com',
39
+ to: simulatorAddress('bounce', 'signup-42'), // bounce+signup-42@simulator.groundmail.dev
40
+ subject: 'Bounce handling test',
41
+ html: '<p>Simulated.</p>',
42
+ })
43
+ // status `bounced`, then an `email.bounced` webhook with `data.test: true`
44
+ ```
45
+
46
+ Outcomes: `delivered`, `bounce`, `block`, `complaint`, `suppressed`, `delayed`. Details:
47
+ https://docs.groundmail.dev/testing
48
+
28
49
  ## Adapters
29
50
 
30
51
  Optional entry points (install peer deps as needed):
@@ -41,6 +62,7 @@ Optional entry points (install peer deps as needed):
41
62
 
42
63
  - Quickstart: https://docs.groundmail.dev/quickstart
43
64
  - Node / Nodemailer: https://docs.groundmail.dev/frameworks/node
65
+ - Testing (test keys, simulator): https://docs.groundmail.dev/testing
44
66
  - API reference: https://docs.groundmail.dev/api/emails
45
67
  - Product: https://groundmail.dev
46
68
 
package/dist/authjs.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { G as Groundmail } from './client-DYUh2ub4.js';
1
+ import { G as Groundmail } from './client-DLz4rpD9.js';
2
2
 
3
3
  /** Subset of Auth.js theme used for the default magic-link email. */
4
4
  type AuthjsTheme = {
package/dist/authjs.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DEFAULT_API_BASE_URL,
3
3
  Groundmail
4
- } from "./chunk-N2LD34ND.js";
4
+ } from "./chunk-MJKAVFFN.js";
5
5
 
6
6
  // src/authjs.ts
7
7
  function escapeHtml(value) {
@@ -1,4 +1,4 @@
1
- import { S as SendEmailResult, G as Groundmail } from './client-DYUh2ub4.js';
1
+ import { S as SendEmailResult, G as Groundmail } from './client-DLz4rpD9.js';
2
2
 
3
3
  type BetterAuthEmailUser = {
4
4
  email: string;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DEFAULT_API_BASE_URL,
3
3
  Groundmail
4
- } from "./chunk-N2LD34ND.js";
4
+ } from "./chunk-MJKAVFFN.js";
5
5
 
6
6
  // src/better-auth.ts
7
7
  function linkHtml(label, url) {
@@ -773,6 +773,19 @@ var MigrationsResource = class {
773
773
  body: JSON.stringify(params)
774
774
  });
775
775
  }
776
+ /** Recent imports (newest first), with totals. */
777
+ async list(params = {}) {
778
+ const query = params.limit ? `?limit=${encodeURIComponent(String(params.limit))}` : "";
779
+ return this.client.request(`/migrations${query}`, { method: "GET" });
780
+ }
781
+ /** The stored report of one import, every request merged. Webhook secrets are not kept. */
782
+ async get(id) {
783
+ return this.client.request(`/migrations/${encodeURIComponent(id)}`, { method: "GET" });
784
+ }
785
+ /** The report as CSV text: one row per domain, webhook and template, plus counts, skips and next steps. */
786
+ async reportCsv(id) {
787
+ return this.client.requestText(`/migrations/${encodeURIComponent(id)}/report`);
788
+ }
776
789
  /** The whole import: follows `next_cursor` until done and returns the combined report. */
777
790
  async run(params, options = {}) {
778
791
  const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
@@ -995,6 +1008,7 @@ var EmailsResource = class {
995
1008
  const search = new URLSearchParams();
996
1009
  if (params.limit != null) search.set("limit", String(params.limit));
997
1010
  if (params.cursor) search.set("cursor", params.cursor);
1011
+ if (params.test != null) search.set("test", String(params.test));
998
1012
  const query = search.toString();
999
1013
  return this.client.request(`/emails${query ? `?${query}` : ""}`, {
1000
1014
  method: "GET"
@@ -1179,7 +1193,10 @@ var KeysResource = class {
1179
1193
  method: "POST",
1180
1194
  body: JSON.stringify({
1181
1195
  ...params.name ? { name: params.name } : {},
1182
- ...params.scope ? { scope: params.scope } : {}
1196
+ ...params.scope && !params.scopes?.length ? { scope: params.scope } : {},
1197
+ ...params.scopes?.length ? { scopes: params.scopes } : {},
1198
+ ...params.allowedDomains?.length ? { allowed_domains: params.allowedDomains } : {},
1199
+ ...params.test ? { test: true } : {}
1183
1200
  })
1184
1201
  });
1185
1202
  }
@@ -1431,6 +1448,18 @@ var Groundmail = class {
1431
1448
  get fetchImpl() {
1432
1449
  return this.options.fetch || fetch;
1433
1450
  }
1451
+ /** GET a text body (CSV downloads). Errors are JSON, like every other route. */
1452
+ async requestText(path) {
1453
+ const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
1454
+ method: "GET",
1455
+ headers: { Authorization: `Bearer ${this.apiKey}` }
1456
+ });
1457
+ if (!response.ok) {
1458
+ const body = await response.json().catch(() => ({}));
1459
+ throw GroundmailError.fromResponse(response.status, body);
1460
+ }
1461
+ return response.text();
1462
+ }
1434
1463
  async request(path, init) {
1435
1464
  const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
1436
1465
  ...init,
package/dist/clerk.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { G as Groundmail, S as SendEmailResult } from './client-DYUh2ub4.js';
1
+ import { G as Groundmail, S as SendEmailResult } from './client-DLz4rpD9.js';
2
2
 
3
3
  /**
4
4
  * Subset of Clerk `email.created` webhook `data` used for delivery.
package/dist/clerk.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DEFAULT_API_BASE_URL,
3
3
  Groundmail
4
- } from "./chunk-N2LD34ND.js";
4
+ } from "./chunk-MJKAVFFN.js";
5
5
 
6
6
  // src/clerk.ts
7
7
  function resolveApiKey(options) {
@@ -78,6 +78,8 @@ type SendEmailResult = {
78
78
  status: string;
79
79
  /** Present when some recipients were held back and the email went to the rest. */
80
80
  held_back?: HeldBackRecipient[];
81
+ /** `true` on a test send (a `gm_test_…` key or only simulator addresses): nothing was delivered. */
82
+ test?: true;
81
83
  };
82
84
  /** `details` on `POST /emails` 422 when Groundmail validation held back every `to` recipient. */
83
85
  type HeldBackErrorDetails = {
@@ -111,6 +113,7 @@ type BatchSendEmailResult = {
111
113
  id?: string;
112
114
  status?: string;
113
115
  held_back?: HeldBackRecipient[];
116
+ test?: true;
114
117
  error?: {
115
118
  message: string;
116
119
  code?: string;
@@ -148,6 +151,11 @@ type EmailListItem = {
148
151
  bounce_type: BounceType | null;
149
152
  /** Recipients Groundmail validation held back before sending, with why. Empty when none were. */
150
153
  held_back: HeldBackRecipient[];
154
+ /**
155
+ * `true` when the email never reached the mail server: sent with a test key or only to
156
+ * simulator addresses. Its status and events are simulated.
157
+ */
158
+ test: boolean;
151
159
  tags: unknown;
152
160
  scheduled_at: string | null;
153
161
  cancelled_at: string | null;
@@ -207,6 +215,8 @@ type ListReceivedEmailsResult = {
207
215
  type ListEmailsParams = {
208
216
  limit?: number;
209
217
  cursor?: string;
218
+ /** `true`: only test emails; `false`: only live ones. Omit for both. */
219
+ test?: boolean;
210
220
  };
211
221
  type ListEmailsResult = {
212
222
  emails: EmailListItem[];
@@ -261,6 +271,12 @@ type ApiKey = {
261
271
  id: string;
262
272
  name: string;
263
273
  scope: ApiKeyScope;
274
+ /** Fine-grained scopes (`emails:send`, `domains:read`, …). */
275
+ scopes?: string[];
276
+ /** Domains the key may send from; empty means any verified domain. */
277
+ allowed_domains?: string[];
278
+ /** A test key (`gm_test_…`): its sends are simulated and never delivered. */
279
+ test?: boolean;
264
280
  prefix: string;
265
281
  created_at: string;
266
282
  last_used_at: string | null;
@@ -277,11 +293,24 @@ type ListApiKeysResult = {
277
293
  type CreateApiKeyParams = {
278
294
  name?: string;
279
295
  scope?: ApiKeyScope;
296
+ /** Fine-grained scopes, e.g. `['emails:send']`. Takes precedence over `scope`. */
297
+ scopes?: string[];
298
+ /** Verified domains the key may send from (needs `emails:send`, not `full`). */
299
+ allowedDomains?: string[];
300
+ /**
301
+ * Create a test key (`gm_test_…`): same scopes and checks as a live key, but sends are
302
+ * never delivered. They get simulated events and webhooks and count toward nothing.
303
+ */
304
+ test?: boolean;
280
305
  };
281
306
  type CreateApiKeyResult = {
282
307
  id: string;
283
308
  name: string;
284
309
  scope: ApiKeyScope;
310
+ scopes?: string[];
311
+ allowed_domains?: string[];
312
+ /** A test key (`gm_test_…`). */
313
+ test?: boolean;
285
314
  prefix: string;
286
315
  token: string;
287
316
  created_at: string;
@@ -326,6 +355,8 @@ type EmailWebhookEventPayload = {
326
355
  bounce_reason?: string | null;
327
356
  /** Set on `email.bounced` and `email.failed` when the email bounced: `hard` or `block`. */
328
357
  bounce_type?: BounceType | null;
358
+ /** `true` on simulated events (test keys, simulator addresses, `POST /emails/test`). Absent on real mail. */
359
+ test?: true;
329
360
  };
330
361
  };
331
362
  /** `contact.unsubscribed` webhook payload (`scope` distinguishes global vs category opt-out). */
@@ -881,7 +912,31 @@ type CreateMigrationParams = {
881
912
  /** `next_cursor` from the previous response, to continue. */
882
913
  cursor?: string;
883
914
  };
915
+ /**
916
+ * Running totals. Suppressions also count by kind: `bounce`, `complaint`,
917
+ * `unsubscribe`, `manual`, and `product_opt_out` (opt-outs that were never
918
+ * global at the old provider; they stop product email only).
919
+ */
884
920
  type MigrationTotals = Record<string, number>;
921
+ /** One DNS change for a domain, compared with what it publishes today. */
922
+ type MigrationDnsChange = {
923
+ /** `add` a record, `update` an existing one to `value` (SPF), `keep` yours (DMARC), or `done`. */
924
+ action: 'add' | 'update' | 'keep' | 'done';
925
+ type: string;
926
+ host: string;
927
+ value: string;
928
+ purpose: string;
929
+ /** What the name holds today, when relevant. */
930
+ current: string | null;
931
+ note: string | null;
932
+ };
933
+ type MigrationDnsPlan = {
934
+ /** False when DNS could not be read; every record is then listed as `add`. */
935
+ checked: boolean;
936
+ changes: MigrationDnsChange[];
937
+ /** Which old-provider records to keep while switching, and when to remove them. */
938
+ keep_old_records: string;
939
+ };
885
940
  type MigrationDomainItem = {
886
941
  name: string;
887
942
  status: MigrationItemStatus;
@@ -893,6 +948,8 @@ type MigrationDomainItem = {
893
948
  value: string;
894
949
  purpose: string;
895
950
  }>;
951
+ /** Null in a preview, or when the import ran out of time to check. */
952
+ dns_plan: MigrationDnsPlan | null;
896
953
  dashboard_url: string | null;
897
954
  message: string | null;
898
955
  };
@@ -964,6 +1021,16 @@ type Migration = {
964
1021
  }> | null;
965
1022
  agent_prompt: string | null;
966
1023
  };
1024
+ /** A stored import, as listed by `migrations.list()`. Previews are not stored. */
1025
+ type MigrationSummary = {
1026
+ id: string;
1027
+ provider: MigrationProvider;
1028
+ status: Migration['status'];
1029
+ resources: MigrationResource[];
1030
+ started_at: string;
1031
+ finished_at: string | null;
1032
+ totals: Partial<Record<MigrationResource, MigrationTotals>>;
1033
+ };
967
1034
  type RunMigrationOptions = {
968
1035
  /** Called after each request with the report so far. */
969
1036
  onProgress?: (migration: Migration) => void;
@@ -1079,6 +1146,18 @@ declare class MigrationsResource {
1079
1146
  create(params: CreateMigrationParams): Promise<{
1080
1147
  migration: Migration;
1081
1148
  }>;
1149
+ /** Recent imports (newest first), with totals. */
1150
+ list(params?: {
1151
+ limit?: number;
1152
+ }): Promise<{
1153
+ data: MigrationSummary[];
1154
+ }>;
1155
+ /** The stored report of one import, every request merged. Webhook secrets are not kept. */
1156
+ get(id: string): Promise<{
1157
+ migration: Migration;
1158
+ }>;
1159
+ /** The report as CSV text: one row per domain, webhook and template, plus counts, skips and next steps. */
1160
+ reportCsv(id: string): Promise<string>;
1082
1161
  /** The whole import: follows `next_cursor` until done and returns the combined report. */
1083
1162
  run(params: CreateMigrationParams, options?: RunMigrationOptions): Promise<Migration>;
1084
1163
  }
@@ -1210,7 +1289,9 @@ declare class Groundmail {
1210
1289
  validate(email: string): Promise<ValidateEmailResult>;
1211
1290
  get baseUrl(): string;
1212
1291
  get fetchImpl(): typeof fetch;
1292
+ /** GET a text body (CSV downloads). Errors are JSON, like every other route. */
1293
+ requestText(path: string): Promise<string>;
1213
1294
  request<T>(path: string, init: RequestInit): Promise<T>;
1214
1295
  }
1215
1296
 
1216
- export { type EmailWebhookEventPayload as $, type ApiErrorBody as A, type BatchSendEmailResult as B, type CategoryUnsubscribedErrorDetails as C, type DnsRecord as D, type CreateSuppressionParams as E, type CreateTemplateParams as F, Groundmail as G, type CreateWebhookParams as H, DEFAULT_API_BASE_URL as I, type DailyRampLimitErrorDetails as J, type DeliverabilityBucket as K, type DeliverabilityDomainInsight as L, type DeliverabilityInsightsResult as M, type DeliverabilityMailServerRelay as N, type DeliverabilityWindow as O, type DnsProviderName as P, type Domain as Q, type DomainBimiAnalysis as R, type SendEmailResult as S, type DomainDetailResult as T, type DomainDmarcAnalysis as U, type DomainDnsAnalysis as V, type DomainStatus as W, type DomainTlsMode as X, type DomainTrackingDns as Y, type DomainTrackingStatus as Z, type EmailListItem as _, type ApiErrorCode as a, mergeMigrations as a$, type EmailWebhookEventType as a0, type EnrollmentSkip as a1, type EventRecord as a2, type GroundmailOptions as a3, type HeldBackErrorDetails as a4, type HeldBackReason as a5, type HeldBackRecipient as a6, type ListApiKeysParams as a7, type ListApiKeysResult as a8, type ListAutomationRunsParams as a9, type SendEmailParams as aA, type SuppressedErrorDetails as aB, type Suppression as aC, type Template as aD, type TemplateFormat as aE, type TemplateLintWarning as aF, type TemplateStarter as aG, type TemplateStatus as aH, type TemplateVersion as aI, type TestEmailWebhookParams as aJ, type TestEmailWebhookResult as aK, type TriggerEventParams as aL, type TriggerEventResult as aM, type TwilioIntegrationResponse as aN, type TwilioIntegrationStatus as aO, type UpdateAutomationParams as aP, type UpdateDomainParams as aQ, type UpdateEmailParams as aR, type UpdateTemplateParams as aS, type UpdateWebhookParams as aT, type ValidateEmailResult as aU, type ValidationReason as aV, type ValidationVerdict as aW, type VerifyDomainResult as aX, type WebhookEndpoint as aY, type WebhookEndpointWithSecret as aZ, type WebhookEventType as a_, type ListAutomationRunsResult as aa, type ListAutomationsResult as ab, type ListDomainsParams as ac, type ListDomainsResult as ad, type ListEmailsParams as ae, type ListEmailsResult as af, type ListReceivedEmailsParams as ag, type ListReceivedEmailsResult as ah, type ListSuppressionsParams as ai, type ListSuppressionsResult as aj, type ListTemplatesParams as ak, type ListTemplatesResult as al, type MailServerSigningStatus as am, type Migration as an, type MigrationDomainItem as ao, type MigrationItemStatus as ap, type MigrationProvider as aq, type MigrationResource as ar, type MigrationTemplateItem as as, type MigrationTotals as at, type MigrationWebhookItem as au, type ReceivedEmail as av, type ReceivedEmailAttachment as aw, type ReceivedEmailAttachmentMeta as ax, type RemoteApplyDnsCredentials as ay, type RunMigrationOptions as az, type ApiKey as b, type ApiKeyScope as c, type ApplyDnsParams as d, type ApplyDnsRecordResult as e, type ApplyDnsResult as f, type Asset as g, type Automation as h, type AutomationResult as i, type AutomationRun as j, type AutomationRunResponse as k, type AutomationRunStatus as l, type AutomationRunStep as m, type AutomationRunStepStatus as n, type AutomationStatus as o, type AutomationStep as p, type AutomationTrigger as q, type BounceType as r, type ConnectTwilioParams as s, type ContactWebhookEventPayload as t, type CreateApiKeyParams as u, type CreateApiKeyResult as v, type CreateAutomationParams as w, type CreateDomainParams as x, type CreateDomainResult as y, type CreateMigrationParams as z };
1297
+ export { type EmailWebhookEventPayload as $, type ApiErrorBody as A, type BatchSendEmailResult as B, type CategoryUnsubscribedErrorDetails as C, type DnsRecord as D, type CreateSuppressionParams as E, type CreateTemplateParams as F, Groundmail as G, type CreateWebhookParams as H, DEFAULT_API_BASE_URL as I, type DailyRampLimitErrorDetails as J, type DeliverabilityBucket as K, type DeliverabilityDomainInsight as L, type DeliverabilityInsightsResult as M, type DeliverabilityMailServerRelay as N, type DeliverabilityWindow as O, type DnsProviderName as P, type Domain as Q, type DomainBimiAnalysis as R, type SendEmailResult as S, type DomainDetailResult as T, type DomainDmarcAnalysis as U, type DomainDnsAnalysis as V, type DomainStatus as W, type DomainTlsMode as X, type DomainTrackingDns as Y, type DomainTrackingStatus as Z, type EmailListItem as _, type ApiErrorCode as a, type WebhookEndpoint as a$, type EmailWebhookEventType as a0, type EnrollmentSkip as a1, type EventRecord as a2, type GroundmailOptions as a3, type HeldBackErrorDetails as a4, type HeldBackReason as a5, type HeldBackRecipient as a6, type ListApiKeysParams as a7, type ListApiKeysResult as a8, type ListAutomationRunsParams as a9, type ReceivedEmailAttachmentMeta as aA, type RemoteApplyDnsCredentials as aB, type RunMigrationOptions as aC, type SendEmailParams as aD, type SuppressedErrorDetails as aE, type Suppression as aF, type Template as aG, type TemplateFormat as aH, type TemplateLintWarning as aI, type TemplateStarter as aJ, type TemplateStatus as aK, type TemplateVersion as aL, type TestEmailWebhookParams as aM, type TestEmailWebhookResult as aN, type TriggerEventParams as aO, type TriggerEventResult as aP, type TwilioIntegrationResponse as aQ, type TwilioIntegrationStatus as aR, type UpdateAutomationParams as aS, type UpdateDomainParams as aT, type UpdateEmailParams as aU, type UpdateTemplateParams as aV, type UpdateWebhookParams as aW, type ValidateEmailResult as aX, type ValidationReason as aY, type ValidationVerdict as aZ, type VerifyDomainResult as a_, type ListAutomationRunsResult as aa, type ListAutomationsResult as ab, type ListDomainsParams as ac, type ListDomainsResult as ad, type ListEmailsParams as ae, type ListEmailsResult as af, type ListReceivedEmailsParams as ag, type ListReceivedEmailsResult as ah, type ListSuppressionsParams as ai, type ListSuppressionsResult as aj, type ListTemplatesParams as ak, type ListTemplatesResult as al, type MailServerSigningStatus as am, type Migration as an, type MigrationDnsChange as ao, type MigrationDnsPlan as ap, type MigrationDomainItem as aq, type MigrationItemStatus as ar, type MigrationProvider as as, type MigrationResource as at, type MigrationSummary as au, type MigrationTemplateItem as av, type MigrationTotals as aw, type MigrationWebhookItem as ax, type ReceivedEmail as ay, type ReceivedEmailAttachment as az, type ApiKey as b, type WebhookEndpointWithSecret as b0, type WebhookEventType as b1, mergeMigrations as b2, type ApiKeyScope as c, type ApplyDnsParams as d, type ApplyDnsRecordResult as e, type ApplyDnsResult as f, type Asset as g, type Automation as h, type AutomationResult as i, type AutomationRun as j, type AutomationRunResponse as k, type AutomationRunStatus as l, type AutomationRunStep as m, type AutomationRunStepStatus as n, type AutomationStatus as o, type AutomationStep as p, type AutomationTrigger as q, type BounceType as r, type ConnectTwilioParams as s, type ContactWebhookEventPayload as t, type CreateApiKeyParams as u, type CreateApiKeyResult as v, type CreateAutomationParams as w, type CreateDomainParams as x, type CreateDomainResult as y, type CreateMigrationParams as z };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as ApiErrorBody, D as DnsRecord } from './client-DYUh2ub4.js';
2
- export { a as ApiErrorCode, b as ApiKey, c as ApiKeyScope, d as ApplyDnsParams, e as ApplyDnsRecordResult, f as ApplyDnsResult, g as Asset, h as Automation, i as AutomationResult, j as AutomationRun, k as AutomationRunResponse, l as AutomationRunStatus, m as AutomationRunStep, n as AutomationRunStepStatus, o as AutomationStatus, p as AutomationStep, q as AutomationTrigger, B as BatchSendEmailResult, r as BounceType, C as CategoryUnsubscribedErrorDetails, s as ConnectTwilioParams, t as ContactWebhookEventPayload, u as CreateApiKeyParams, v as CreateApiKeyResult, w as CreateAutomationParams, x as CreateDomainParams, y as CreateDomainResult, z as CreateMigrationParams, E as CreateSuppressionParams, F as CreateTemplateParams, H as CreateWebhookParams, I as DEFAULT_API_BASE_URL, J as DailyRampLimitErrorDetails, K as DeliverabilityBucket, L as DeliverabilityDomainInsight, M as DeliverabilityInsightsResult, N as DeliverabilityMailServerRelay, O as DeliverabilityWindow, P as DnsProviderName, Q as Domain, R as DomainBimiAnalysis, T as DomainDetailResult, U as DomainDmarcAnalysis, V as DomainDnsAnalysis, W as DomainStatus, X as DomainTlsMode, Y as DomainTrackingDns, Z as DomainTrackingStatus, _ as EmailListItem, $ as EmailWebhookEventPayload, a0 as EmailWebhookEventType, a1 as EnrollmentSkip, a2 as EventRecord, G as Groundmail, a3 as GroundmailOptions, a4 as HeldBackErrorDetails, a5 as HeldBackReason, a6 as HeldBackRecipient, a7 as ListApiKeysParams, a8 as ListApiKeysResult, a9 as ListAutomationRunsParams, aa as ListAutomationRunsResult, ab as ListAutomationsResult, ac as ListDomainsParams, ad as ListDomainsResult, ae as ListEmailsParams, af as ListEmailsResult, ag as ListReceivedEmailsParams, ah as ListReceivedEmailsResult, ai as ListSuppressionsParams, aj as ListSuppressionsResult, ak as ListTemplatesParams, al as ListTemplatesResult, am as MailServerSigningStatus, an as Migration, ao as MigrationDomainItem, ap as MigrationItemStatus, aq as MigrationProvider, ar as MigrationResource, as as MigrationTemplateItem, at as MigrationTotals, au as MigrationWebhookItem, av as ReceivedEmail, aw as ReceivedEmailAttachment, ax as ReceivedEmailAttachmentMeta, ay as RemoteApplyDnsCredentials, az as RunMigrationOptions, aA as SendEmailParams, S as SendEmailResult, aB as SuppressedErrorDetails, aC as Suppression, aD as Template, aE as TemplateFormat, aF as TemplateLintWarning, aG as TemplateStarter, aH as TemplateStatus, aI as TemplateVersion, aJ as TestEmailWebhookParams, aK as TestEmailWebhookResult, aL as TriggerEventParams, aM as TriggerEventResult, aN as TwilioIntegrationResponse, aO as TwilioIntegrationStatus, aP as UpdateAutomationParams, aQ as UpdateDomainParams, aR as UpdateEmailParams, aS as UpdateTemplateParams, aT as UpdateWebhookParams, aU as ValidateEmailResult, aV as ValidationReason, aW as ValidationVerdict, aX as VerifyDomainResult, aY as WebhookEndpoint, aZ as WebhookEndpointWithSecret, a_ as WebhookEventType, a$ as mergeMigrations } from './client-DYUh2ub4.js';
1
+ import { A as ApiErrorBody, D as DnsRecord } from './client-DLz4rpD9.js';
2
+ export { a as ApiErrorCode, b as ApiKey, c as ApiKeyScope, d as ApplyDnsParams, e as ApplyDnsRecordResult, f as ApplyDnsResult, g as Asset, h as Automation, i as AutomationResult, j as AutomationRun, k as AutomationRunResponse, l as AutomationRunStatus, m as AutomationRunStep, n as AutomationRunStepStatus, o as AutomationStatus, p as AutomationStep, q as AutomationTrigger, B as BatchSendEmailResult, r as BounceType, C as CategoryUnsubscribedErrorDetails, s as ConnectTwilioParams, t as ContactWebhookEventPayload, u as CreateApiKeyParams, v as CreateApiKeyResult, w as CreateAutomationParams, x as CreateDomainParams, y as CreateDomainResult, z as CreateMigrationParams, E as CreateSuppressionParams, F as CreateTemplateParams, H as CreateWebhookParams, I as DEFAULT_API_BASE_URL, J as DailyRampLimitErrorDetails, K as DeliverabilityBucket, L as DeliverabilityDomainInsight, M as DeliverabilityInsightsResult, N as DeliverabilityMailServerRelay, O as DeliverabilityWindow, P as DnsProviderName, Q as Domain, R as DomainBimiAnalysis, T as DomainDetailResult, U as DomainDmarcAnalysis, V as DomainDnsAnalysis, W as DomainStatus, X as DomainTlsMode, Y as DomainTrackingDns, Z as DomainTrackingStatus, _ as EmailListItem, $ as EmailWebhookEventPayload, a0 as EmailWebhookEventType, a1 as EnrollmentSkip, a2 as EventRecord, G as Groundmail, a3 as GroundmailOptions, a4 as HeldBackErrorDetails, a5 as HeldBackReason, a6 as HeldBackRecipient, a7 as ListApiKeysParams, a8 as ListApiKeysResult, a9 as ListAutomationRunsParams, aa as ListAutomationRunsResult, ab as ListAutomationsResult, ac as ListDomainsParams, ad as ListDomainsResult, ae as ListEmailsParams, af as ListEmailsResult, ag as ListReceivedEmailsParams, ah as ListReceivedEmailsResult, ai as ListSuppressionsParams, aj as ListSuppressionsResult, ak as ListTemplatesParams, al as ListTemplatesResult, am as MailServerSigningStatus, an as Migration, ao as MigrationDnsChange, ap as MigrationDnsPlan, aq as MigrationDomainItem, ar as MigrationItemStatus, as as MigrationProvider, at as MigrationResource, au as MigrationSummary, av as MigrationTemplateItem, aw as MigrationTotals, ax as MigrationWebhookItem, ay as ReceivedEmail, az as ReceivedEmailAttachment, aA as ReceivedEmailAttachmentMeta, aB as RemoteApplyDnsCredentials, aC as RunMigrationOptions, aD as SendEmailParams, S as SendEmailResult, aE as SuppressedErrorDetails, aF as Suppression, aG as Template, aH as TemplateFormat, aI as TemplateLintWarning, aJ as TemplateStarter, aK as TemplateStatus, aL as TemplateVersion, aM as TestEmailWebhookParams, aN as TestEmailWebhookResult, aO as TriggerEventParams, aP as TriggerEventResult, aQ as TwilioIntegrationResponse, aR as TwilioIntegrationStatus, aS as UpdateAutomationParams, aT as UpdateDomainParams, aU as UpdateEmailParams, aV as UpdateTemplateParams, aW as UpdateWebhookParams, aX as ValidateEmailResult, aY as ValidationReason, aZ as ValidationVerdict, a_ as VerifyDomainResult, a$ as WebhookEndpoint, b0 as WebhookEndpointWithSecret, b1 as WebhookEventType, b2 as mergeMigrations } from './client-DLz4rpD9.js';
3
3
 
4
4
  type GroundmailErrorOptions = {
5
5
  code?: string;
@@ -43,6 +43,40 @@ declare function buildSignatureHeader(secret: string, payload: string, timestamp
43
43
  */
44
44
  declare function verifyWebhookSignature(secret: string, payload: string, header: string | null | undefined, toleranceSec?: number): boolean;
45
45
 
46
+ /**
47
+ * Test mode helpers.
48
+ *
49
+ * Simulator addresses at `simulator.groundmail.dev` produce one outcome each, with
50
+ * the same status and webhooks as real mail, and never leave Groundmail. They work
51
+ * with live and test keys. Plus addressing tells sends apart:
52
+ *
53
+ * ```ts
54
+ * import { simulatorAddress } from 'groundmail'
55
+ * await groundmail.emails.send({ from, to: simulatorAddress('bounce', 'signup-42'), subject, html })
56
+ * // → bounce+signup-42@simulator.groundmail.dev: status `bounced`, `email.bounced` webhook
57
+ * ```
58
+ *
59
+ * Docs: https://docs.groundmail.dev/testing
60
+ */
61
+ declare const SIMULATOR_DOMAIN = "simulator.groundmail.dev";
62
+ declare const SIMULATOR_OUTCOMES: readonly ["delivered", "bounce", "block", "complaint", "suppressed", "delayed"];
63
+ /**
64
+ * - `delivered`: `email.delivered`
65
+ * - `bounce`: hard bounce, `email.bounced` with `bounce_type: "hard"`
66
+ * - `block`: block bounce, `email.bounced` with `bounce_type: "block"`
67
+ * - `complaint`: `email.delivered`, then `email.complained`
68
+ * - `suppressed`: refused like a suppressed recipient (422), `email.suppressed`
69
+ * - `delayed`: `email.delivery_delayed`
70
+ */
71
+ type SimulatorOutcome = (typeof SIMULATOR_OUTCOMES)[number];
72
+ /** `simulatorAddress('bounce')` → `bounce@simulator.groundmail.dev`; a tag adds `+tag`. */
73
+ declare function simulatorAddress(outcome: SimulatorOutcome, tag?: string): string;
74
+ /** True for any address at the simulator domain. */
75
+ declare function isSimulatorAddress(address: string): boolean;
76
+ /** Test keys start with `gm_test_`. */
77
+ declare const TEST_API_KEY_PREFIX = "gm_test_";
78
+ declare function isTestApiKey(key: string | null | undefined): boolean;
79
+
46
80
  /** Merge `include:host` into an existing SPF record (or create a minimal one). */
47
81
  declare function mergeSpfInclude(existing: string | null | undefined, includeHost: string): string;
48
82
 
@@ -75,4 +109,4 @@ declare function applyDnsRecords(input: {
75
109
  fetchImpl?: typeof fetch;
76
110
  }): Promise<ApplyDnsResult>;
77
111
 
78
- export { DnsRecord, GroundmailError, type GroundmailErrorOptions, applyDnsRecords, buildSignatureHeader, mergeSpfInclude, signWebhookPayload, verifyWebhookSignature };
112
+ export { DnsRecord, GroundmailError, type GroundmailErrorOptions, SIMULATOR_DOMAIN, SIMULATOR_OUTCOMES, type SimulatorOutcome, TEST_API_KEY_PREFIX, applyDnsRecords, buildSignatureHeader, isSimulatorAddress, isTestApiKey, mergeSpfInclude, signWebhookPayload, simulatorAddress, verifyWebhookSignature };
package/dist/index.js CHANGED
@@ -8,15 +8,41 @@ import {
8
8
  mergeSpfInclude,
9
9
  signWebhookPayload,
10
10
  verifyWebhookSignature
11
- } from "./chunk-N2LD34ND.js";
11
+ } from "./chunk-MJKAVFFN.js";
12
+
13
+ // src/simulator.ts
14
+ var SIMULATOR_DOMAIN = "simulator.groundmail.dev";
15
+ var SIMULATOR_OUTCOMES = ["delivered", "bounce", "block", "complaint", "suppressed", "delayed"];
16
+ function simulatorAddress(outcome, tag) {
17
+ if (!SIMULATOR_OUTCOMES.includes(outcome)) {
18
+ throw new Error(`Unknown simulator outcome: ${outcome}`);
19
+ }
20
+ const cleanTag = tag?.trim().replace(/[^A-Za-z0-9._-]+/g, "-");
21
+ return `${outcome}${cleanTag ? `+${cleanTag}` : ""}@${SIMULATOR_DOMAIN}`;
22
+ }
23
+ function isSimulatorAddress(address) {
24
+ const match = address.match(/<([^>]+)>/);
25
+ const bare = (match ? match[1] : address).trim().toLowerCase();
26
+ return bare.endsWith(`@${SIMULATOR_DOMAIN}`);
27
+ }
28
+ var TEST_API_KEY_PREFIX = "gm_test_";
29
+ function isTestApiKey(key) {
30
+ return Boolean(key?.startsWith(TEST_API_KEY_PREFIX));
31
+ }
12
32
  export {
13
33
  DEFAULT_API_BASE_URL,
14
34
  Groundmail,
15
35
  GroundmailError,
36
+ SIMULATOR_DOMAIN,
37
+ SIMULATOR_OUTCOMES,
38
+ TEST_API_KEY_PREFIX,
16
39
  applyDnsRecords,
17
40
  buildSignatureHeader,
41
+ isSimulatorAddress,
42
+ isTestApiKey,
18
43
  mergeMigrations,
19
44
  mergeSpfInclude,
20
45
  signWebhookPayload,
46
+ simulatorAddress,
21
47
  verifyWebhookSignature
22
48
  };
package/dist/payload.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { G as Groundmail } from './client-DYUh2ub4.js';
1
+ import { G as Groundmail } from './client-DLz4rpD9.js';
2
2
 
3
3
  /**
4
4
  * Minimal Payload `SendEmailOptions` / address shapes used by this adapter.
package/dist/payload.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DEFAULT_API_BASE_URL,
3
3
  Groundmail
4
- } from "./chunk-N2LD34ND.js";
4
+ } from "./chunk-MJKAVFFN.js";
5
5
 
6
6
  // src/payload.ts
7
7
  function resolveApiKey(args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groundmail",
3
- "version": "0.15.1",
3
+ "version": "0.17.0",
4
4
  "description": "Groundmail transactional email API client",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -89,7 +89,7 @@
89
89
  "prepublishOnly": "npm run build && npm run build:nodemailer-bridge"
90
90
  },
91
91
  "dependencies": {
92
- "groundmail-nodemailer": "0.15.1"
92
+ "groundmail-nodemailer": "0.15.3"
93
93
  },
94
94
  "devDependencies": {
95
95
  "@types/nodemailer": "^8.0.1",