groundmail 0.16.0 → 0.19.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 +66 -0
- package/dist/authjs.d.ts +1 -1
- package/dist/authjs.js +1 -1
- package/dist/better-auth.d.ts +1 -1
- package/dist/better-auth.js +1 -1
- package/dist/{chunk-JYLRUBEJ.js → chunk-2RKWN4OE.js} +129 -7
- package/dist/clerk.d.ts +1 -1
- package/dist/clerk.js +1 -1
- package/dist/{client-DXYsWgOT.d.ts → client-H0RF4KEK.d.ts} +375 -7
- package/dist/index.d.ts +37 -3
- package/dist/index.js +27 -1
- package/dist/payload.d.ts +1 -1
- package/dist/payload.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -25,6 +25,70 @@ 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
|
+
|
|
49
|
+
## Tenants
|
|
50
|
+
|
|
51
|
+
If you send email for your own customers, create one tenant per customer. Each tenant has its
|
|
52
|
+
own domains, suppression list, stats, pause switch and daily limit, so one customer's bounces
|
|
53
|
+
or complaints never affect the others. Tenants are included on the Scale and Dedicated plans.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
await tx.tenants.create({ external_id: 'cus_123', name: 'Acme', daily_limit: 5000 })
|
|
57
|
+
await tx.tenants.domains.assign('cus_123', 'mail.acme.com')
|
|
58
|
+
|
|
59
|
+
await tx.emails.send({
|
|
60
|
+
from: 'billing@mail.acme.com',
|
|
61
|
+
to: 'user@example.com',
|
|
62
|
+
subject: 'Your invoice',
|
|
63
|
+
html: '<p>Thanks!</p>',
|
|
64
|
+
tenant: 'cus_123',
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const { stats } = await tx.tenants.stats('cus_123', { days: 7 })
|
|
68
|
+
await tx.tenants.pause('cus_123', 'Unpaid invoice')
|
|
69
|
+
await tx.tenants.resume('cus_123')
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`tenant` (your external id or the Groundmail id) also works on `emails.list`, `domains.create` and
|
|
73
|
+
`domains.list`, `suppressions`, `apiKeys.create` (a key limited to one tenant) and
|
|
74
|
+
`emails.testWebhook`. Webhook endpoints take `tenants: ['cus_123']` to receive only those
|
|
75
|
+
tenants' email events. Details: https://docs.groundmail.dev/tenants
|
|
76
|
+
|
|
77
|
+
## DMARC monitoring
|
|
78
|
+
|
|
79
|
+
Free for every domain. Turn it on, publish the record it returns, and read sources, pass rates
|
|
80
|
+
and whether it's safe to enforce once the daily reports arrive:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
const { dmarc } = await tx.dmarc.enable('yourdomain.com') // or { apply: 'cloudflare' }
|
|
84
|
+
console.log(dmarc.dns.suggested_record) // your record with our report address in rua=
|
|
85
|
+
|
|
86
|
+
const report = await tx.dmarc.get('yourdomain.com')
|
|
87
|
+
if (report.dmarc.enabled) console.log(report.dmarc.readiness.label, report.dmarc.sources)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Details: https://docs.groundmail.dev/dmarc
|
|
91
|
+
|
|
28
92
|
## Adapters
|
|
29
93
|
|
|
30
94
|
Optional entry points (install peer deps as needed):
|
|
@@ -41,6 +105,8 @@ Optional entry points (install peer deps as needed):
|
|
|
41
105
|
|
|
42
106
|
- Quickstart: https://docs.groundmail.dev/quickstart
|
|
43
107
|
- Node / Nodemailer: https://docs.groundmail.dev/frameworks/node
|
|
108
|
+
- Testing (test keys, simulator): https://docs.groundmail.dev/testing
|
|
109
|
+
- DMARC monitoring: https://docs.groundmail.dev/dmarc
|
|
44
110
|
- API reference: https://docs.groundmail.dev/api/emails
|
|
45
111
|
- Product: https://groundmail.dev
|
|
46
112
|
|
package/dist/authjs.d.ts
CHANGED
package/dist/authjs.js
CHANGED
package/dist/better-auth.d.ts
CHANGED
package/dist/better-auth.js
CHANGED
|
@@ -895,6 +895,32 @@ var AutomationsResource = class {
|
|
|
895
895
|
}
|
|
896
896
|
};
|
|
897
897
|
|
|
898
|
+
// src/dmarc.ts
|
|
899
|
+
var DmarcResource = class {
|
|
900
|
+
constructor(client) {
|
|
901
|
+
this.client = client;
|
|
902
|
+
}
|
|
903
|
+
path(domain) {
|
|
904
|
+
return `/domains/${encodeURIComponent(domain)}/dmarc`;
|
|
905
|
+
}
|
|
906
|
+
/** Monitoring status and the last 30 days of reports. `domain` is the domain id or name. */
|
|
907
|
+
async get(domain) {
|
|
908
|
+
return this.client.request(this.path(domain), { method: "GET" });
|
|
909
|
+
}
|
|
910
|
+
/** Turn monitoring on. Returns the report address and the DMARC record to publish. */
|
|
911
|
+
async enable(domain, params = {}) {
|
|
912
|
+
return this.client.request(this.path(domain), { method: "POST", body: JSON.stringify(params) });
|
|
913
|
+
}
|
|
914
|
+
/** Turn the weekly digest email on or off. */
|
|
915
|
+
async update(domain, params) {
|
|
916
|
+
return this.client.request(this.path(domain), { method: "PATCH", body: JSON.stringify(params) });
|
|
917
|
+
}
|
|
918
|
+
/** Stop monitoring. Remove the address from your DMARC record's rua= tag too. */
|
|
919
|
+
async disable(domain) {
|
|
920
|
+
return this.client.request(this.path(domain), { method: "DELETE" });
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
|
|
898
924
|
// src/domains.ts
|
|
899
925
|
var DomainsResource = class {
|
|
900
926
|
constructor(client) {
|
|
@@ -906,6 +932,7 @@ var DomainsResource = class {
|
|
|
906
932
|
if (params.cursor) search.set("cursor", params.cursor);
|
|
907
933
|
const inboundEnabled = params.inbound_enabled ?? params.inboundEnabled;
|
|
908
934
|
if (inboundEnabled !== void 0) search.set("inbound_enabled", String(inboundEnabled));
|
|
935
|
+
if (params.tenant) search.set("tenant", params.tenant);
|
|
909
936
|
const query = search.toString();
|
|
910
937
|
return this.client.request(`/domains${query ? `?${query}` : ""}`, { method: "GET" });
|
|
911
938
|
}
|
|
@@ -918,7 +945,8 @@ var DomainsResource = class {
|
|
|
918
945
|
method: "POST",
|
|
919
946
|
body: JSON.stringify({
|
|
920
947
|
name: params.name,
|
|
921
|
-
...inboundEnabled !== void 0 ? { inbound_enabled: inboundEnabled } : {}
|
|
948
|
+
...inboundEnabled !== void 0 ? { inbound_enabled: inboundEnabled } : {},
|
|
949
|
+
...params.tenant ? { tenant: params.tenant } : {}
|
|
922
950
|
})
|
|
923
951
|
});
|
|
924
952
|
}
|
|
@@ -1008,6 +1036,8 @@ var EmailsResource = class {
|
|
|
1008
1036
|
const search = new URLSearchParams();
|
|
1009
1037
|
if (params.limit != null) search.set("limit", String(params.limit));
|
|
1010
1038
|
if (params.cursor) search.set("cursor", params.cursor);
|
|
1039
|
+
if (params.test != null) search.set("test", String(params.test));
|
|
1040
|
+
if (params.tenant) search.set("tenant", params.tenant);
|
|
1011
1041
|
const query = search.toString();
|
|
1012
1042
|
return this.client.request(`/emails${query ? `?${query}` : ""}`, {
|
|
1013
1043
|
method: "GET"
|
|
@@ -1073,7 +1103,8 @@ var EmailsResource = class {
|
|
|
1073
1103
|
...params.email_id !== void 0 ? { email_id: params.email_id } : {},
|
|
1074
1104
|
...params.webhook_id !== void 0 ? { webhook_id: params.webhook_id } : {},
|
|
1075
1105
|
...params.sync !== void 0 ? { sync: params.sync } : {},
|
|
1076
|
-
...params.deliver !== void 0 ? { deliver: params.deliver } : {}
|
|
1106
|
+
...params.deliver !== void 0 ? { deliver: params.deliver } : {},
|
|
1107
|
+
...params.tenant !== void 0 ? { tenant: params.tenant } : {}
|
|
1077
1108
|
})
|
|
1078
1109
|
});
|
|
1079
1110
|
}
|
|
@@ -1125,6 +1156,7 @@ var EmailsResource = class {
|
|
|
1125
1156
|
}
|
|
1126
1157
|
if (params.unsubscribe !== void 0) payload.unsubscribe = params.unsubscribe;
|
|
1127
1158
|
if (params.category !== void 0) payload.category = params.category;
|
|
1159
|
+
if (params.tenant !== void 0) payload.tenant = params.tenant;
|
|
1128
1160
|
return payload;
|
|
1129
1161
|
}
|
|
1130
1162
|
};
|
|
@@ -1192,7 +1224,11 @@ var KeysResource = class {
|
|
|
1192
1224
|
method: "POST",
|
|
1193
1225
|
body: JSON.stringify({
|
|
1194
1226
|
...params.name ? { name: params.name } : {},
|
|
1195
|
-
...params.scope ? { scope: params.scope } : {}
|
|
1227
|
+
...params.scope && !params.scopes?.length ? { scope: params.scope } : {},
|
|
1228
|
+
...params.scopes?.length ? { scopes: params.scopes } : {},
|
|
1229
|
+
...params.allowedDomains?.length ? { allowed_domains: params.allowedDomains } : {},
|
|
1230
|
+
...params.test ? { test: true } : {},
|
|
1231
|
+
...params.tenant ? { tenant: params.tenant } : {}
|
|
1196
1232
|
})
|
|
1197
1233
|
});
|
|
1198
1234
|
}
|
|
@@ -1271,6 +1307,7 @@ var SuppressionsResource = class {
|
|
|
1271
1307
|
if (params.limit != null) search.set("limit", String(params.limit));
|
|
1272
1308
|
if (params.cursor) search.set("cursor", params.cursor);
|
|
1273
1309
|
if (params.email) search.set("email", params.email);
|
|
1310
|
+
if (params.tenant) search.set("tenant", params.tenant);
|
|
1274
1311
|
const query = search.toString();
|
|
1275
1312
|
return this.client.request(`/suppressions${query ? `?${query}` : ""}`, { method: "GET" });
|
|
1276
1313
|
}
|
|
@@ -1280,10 +1317,15 @@ var SuppressionsResource = class {
|
|
|
1280
1317
|
body: JSON.stringify(params)
|
|
1281
1318
|
});
|
|
1282
1319
|
}
|
|
1283
|
-
/**
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1320
|
+
/**
|
|
1321
|
+
* Remove by suppression id or email address. Pass `tenant` to remove an address
|
|
1322
|
+
* from that tenant's own list (by email address).
|
|
1323
|
+
*/
|
|
1324
|
+
async remove(idOrEmail, options = {}) {
|
|
1325
|
+
if (idOrEmail.includes("@") || options.tenant) {
|
|
1326
|
+
const search = new URLSearchParams({ email: idOrEmail });
|
|
1327
|
+
if (options.tenant) search.set("tenant", options.tenant);
|
|
1328
|
+
return this.client.request(`/suppressions?${search.toString()}`, {
|
|
1287
1329
|
method: "DELETE"
|
|
1288
1330
|
});
|
|
1289
1331
|
}
|
|
@@ -1369,6 +1411,84 @@ var TemplatesResource = class {
|
|
|
1369
1411
|
}
|
|
1370
1412
|
};
|
|
1371
1413
|
|
|
1414
|
+
// src/tenants.ts
|
|
1415
|
+
var TenantsResource = class {
|
|
1416
|
+
constructor(client) {
|
|
1417
|
+
this.client = client;
|
|
1418
|
+
this.domains = new TenantDomainsResource(client);
|
|
1419
|
+
}
|
|
1420
|
+
async list(params = {}) {
|
|
1421
|
+
const search = new URLSearchParams();
|
|
1422
|
+
if (params.limit != null) search.set("limit", String(params.limit));
|
|
1423
|
+
if (params.cursor) search.set("cursor", params.cursor);
|
|
1424
|
+
if (params.status) search.set("status", params.status);
|
|
1425
|
+
const query = search.toString();
|
|
1426
|
+
return this.client.request(`/tenants${query ? `?${query}` : ""}`, { method: "GET" });
|
|
1427
|
+
}
|
|
1428
|
+
async create(params) {
|
|
1429
|
+
return this.client.request("/tenants", {
|
|
1430
|
+
method: "POST",
|
|
1431
|
+
body: JSON.stringify(params)
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
async get(tenant) {
|
|
1435
|
+
return this.client.request(`/tenants/${encodeURIComponent(tenant)}`, { method: "GET" });
|
|
1436
|
+
}
|
|
1437
|
+
async update(tenant, params) {
|
|
1438
|
+
return this.client.request(`/tenants/${encodeURIComponent(tenant)}`, {
|
|
1439
|
+
method: "PATCH",
|
|
1440
|
+
body: JSON.stringify(params)
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
/** Stop the tenant's new sends until you resume it. */
|
|
1444
|
+
async pause(tenant, reason) {
|
|
1445
|
+
return this.update(tenant, { status: "paused", ...reason ? { reason } : {} });
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Let a tenant you paused send again. A tenant Groundmail paused (`pause_source`
|
|
1449
|
+
* `automated` or `admin`) cannot be resumed this way: contact support.
|
|
1450
|
+
*/
|
|
1451
|
+
async resume(tenant) {
|
|
1452
|
+
return this.update(tenant, { status: "active" });
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Delete a tenant and its suppression list. Its domains go back to the account,
|
|
1456
|
+
* its emails keep their history, and API keys limited to it stop working.
|
|
1457
|
+
*/
|
|
1458
|
+
async delete(tenant) {
|
|
1459
|
+
return this.client.request(`/tenants/${encodeURIComponent(tenant)}`, { method: "DELETE" });
|
|
1460
|
+
}
|
|
1461
|
+
/** Email counts and rates over the last `days` (1–90, default 7), plus the tenant's ramp today. */
|
|
1462
|
+
async stats(tenant, params = {}) {
|
|
1463
|
+
const query = params.days != null ? `?days=${encodeURIComponent(String(params.days))}` : "";
|
|
1464
|
+
return this.client.request(`/tenants/${encodeURIComponent(tenant)}/stats${query}`, {
|
|
1465
|
+
method: "GET"
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
var TenantDomainsResource = class {
|
|
1470
|
+
constructor(client) {
|
|
1471
|
+
this.client = client;
|
|
1472
|
+
}
|
|
1473
|
+
async list(tenant) {
|
|
1474
|
+
return this.client.request(`/tenants/${encodeURIComponent(tenant)}/domains`, { method: "GET" });
|
|
1475
|
+
}
|
|
1476
|
+
/** Give an existing domain of the account (name or id) to the tenant. Only that tenant can then send from it. */
|
|
1477
|
+
async assign(tenant, domain) {
|
|
1478
|
+
return this.client.request(`/tenants/${encodeURIComponent(tenant)}/domains`, {
|
|
1479
|
+
method: "POST",
|
|
1480
|
+
body: JSON.stringify({ domain })
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
/** Give the tenant's domain (name or id) back to the whole account. The domain itself stays. */
|
|
1484
|
+
async unassign(tenant, domain) {
|
|
1485
|
+
return this.client.request(
|
|
1486
|
+
`/tenants/${encodeURIComponent(tenant)}/domains/${encodeURIComponent(domain)}`,
|
|
1487
|
+
{ method: "DELETE" }
|
|
1488
|
+
);
|
|
1489
|
+
}
|
|
1490
|
+
};
|
|
1491
|
+
|
|
1372
1492
|
// src/webhooks/index.ts
|
|
1373
1493
|
var WebhooksResource = class {
|
|
1374
1494
|
constructor(client) {
|
|
@@ -1425,7 +1545,9 @@ var Groundmail = class {
|
|
|
1425
1545
|
this.smtpCredentials = new SmtpCredentialsResource(this);
|
|
1426
1546
|
this.suppressions = new SuppressionsResource(this);
|
|
1427
1547
|
this.templates = new TemplatesResource(this);
|
|
1548
|
+
this.tenants = new TenantsResource(this);
|
|
1428
1549
|
this.migrations = new MigrationsResource(this);
|
|
1550
|
+
this.dmarc = new DmarcResource(this);
|
|
1429
1551
|
}
|
|
1430
1552
|
/**
|
|
1431
1553
|
* Check one address with Groundmail validation before you accept it (for
|
package/dist/clerk.d.ts
CHANGED
package/dist/clerk.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
type ApiErrorCode = 'validation_error' | 'unauthorized' | 'forbidden' | 'not_found' | 'conflict' | 'plan_limit'
|
|
2
2
|
/** 429: the send would go over a new account's daily ramp-up limit; see `details.resets_at`. */
|
|
3
|
-
| 'daily_ramp_limit'
|
|
3
|
+
| 'daily_ramp_limit'
|
|
4
|
+
/** 403: the request names a tenant, but tenants are not enabled for this account (Scale and Dedicated plans). */
|
|
5
|
+
| 'tenants_not_enabled' | 'domain_limit' | 'idempotency_conflict' | 'rate_limited' | 'upstream_error' | 'not_configured' | 'internal_error';
|
|
4
6
|
type SendEmailParams = {
|
|
5
7
|
from: string | {
|
|
6
8
|
email: string;
|
|
@@ -59,6 +61,12 @@ type SendEmailParams = {
|
|
|
59
61
|
* is not accepted: Groundmail is for transactional and product email.
|
|
60
62
|
*/
|
|
61
63
|
category?: 'transactional' | 'product';
|
|
64
|
+
/**
|
|
65
|
+
* Send as one of your tenants (its external id or Groundmail id). The send uses that
|
|
66
|
+
* tenant's domains, suppression list, pause switch and daily limit. Omit to send from
|
|
67
|
+
* the account as a whole, or from a tenant's own domain to use that tenant automatically.
|
|
68
|
+
*/
|
|
69
|
+
tenant?: string;
|
|
62
70
|
};
|
|
63
71
|
/**
|
|
64
72
|
* Why Groundmail validation held a recipient back before sending:
|
|
@@ -78,6 +86,8 @@ type SendEmailResult = {
|
|
|
78
86
|
status: string;
|
|
79
87
|
/** Present when some recipients were held back and the email went to the rest. */
|
|
80
88
|
held_back?: HeldBackRecipient[];
|
|
89
|
+
/** `true` on a test send (a `gm_test_…` key or only simulator addresses): nothing was delivered. */
|
|
90
|
+
test?: true;
|
|
81
91
|
};
|
|
82
92
|
/** `details` on `POST /emails` 422 when Groundmail validation held back every `to` recipient. */
|
|
83
93
|
type HeldBackErrorDetails = {
|
|
@@ -111,6 +121,7 @@ type BatchSendEmailResult = {
|
|
|
111
121
|
id?: string;
|
|
112
122
|
status?: string;
|
|
113
123
|
held_back?: HeldBackRecipient[];
|
|
124
|
+
test?: true;
|
|
114
125
|
error?: {
|
|
115
126
|
message: string;
|
|
116
127
|
code?: string;
|
|
@@ -148,6 +159,11 @@ type EmailListItem = {
|
|
|
148
159
|
bounce_type: BounceType | null;
|
|
149
160
|
/** Recipients Groundmail validation held back before sending, with why. Empty when none were. */
|
|
150
161
|
held_back: HeldBackRecipient[];
|
|
162
|
+
/**
|
|
163
|
+
* `true` when the email never reached the mail server: sent with a test key or only to
|
|
164
|
+
* simulator addresses. Its status and events are simulated.
|
|
165
|
+
*/
|
|
166
|
+
test: boolean;
|
|
151
167
|
tags: unknown;
|
|
152
168
|
scheduled_at: string | null;
|
|
153
169
|
cancelled_at: string | null;
|
|
@@ -159,6 +175,8 @@ type EmailListItem = {
|
|
|
159
175
|
opened_at: string | null;
|
|
160
176
|
/** First tracked-link click; requires click tracking on the sending domain. */
|
|
161
177
|
clicked_at: string | null;
|
|
178
|
+
/** The tenant's external id. Present only when the email was sent as a tenant. */
|
|
179
|
+
tenant?: string | null;
|
|
162
180
|
};
|
|
163
181
|
type ReceivedEmailAttachmentMeta = {
|
|
164
182
|
filename: string;
|
|
@@ -207,6 +225,10 @@ type ListReceivedEmailsResult = {
|
|
|
207
225
|
type ListEmailsParams = {
|
|
208
226
|
limit?: number;
|
|
209
227
|
cursor?: string;
|
|
228
|
+
/** `true`: only test emails; `false`: only live ones. Omit for both. */
|
|
229
|
+
test?: boolean;
|
|
230
|
+
/** Only this tenant's emails (external id or Groundmail id). */
|
|
231
|
+
tenant?: string;
|
|
210
232
|
};
|
|
211
233
|
type ListEmailsResult = {
|
|
212
234
|
emails: EmailListItem[];
|
|
@@ -261,6 +283,14 @@ type ApiKey = {
|
|
|
261
283
|
id: string;
|
|
262
284
|
name: string;
|
|
263
285
|
scope: ApiKeyScope;
|
|
286
|
+
/** Fine-grained scopes (`emails:send`, `domains:read`, …). */
|
|
287
|
+
scopes?: string[];
|
|
288
|
+
/** Domains the key may send from; empty means any verified domain. */
|
|
289
|
+
allowed_domains?: string[];
|
|
290
|
+
/** A test key (`gm_test_…`): its sends are simulated and never delivered. */
|
|
291
|
+
test?: boolean;
|
|
292
|
+
/** The tenant this key is limited to (external id). Absent or null for an account-wide key. */
|
|
293
|
+
tenant?: string | null;
|
|
264
294
|
prefix: string;
|
|
265
295
|
created_at: string;
|
|
266
296
|
last_used_at: string | null;
|
|
@@ -277,11 +307,31 @@ type ListApiKeysResult = {
|
|
|
277
307
|
type CreateApiKeyParams = {
|
|
278
308
|
name?: string;
|
|
279
309
|
scope?: ApiKeyScope;
|
|
310
|
+
/** Fine-grained scopes, e.g. `['emails:send']`. Takes precedence over `scope`. */
|
|
311
|
+
scopes?: string[];
|
|
312
|
+
/** Verified domains the key may send from (needs `emails:send`, not `full`). */
|
|
313
|
+
allowedDomains?: string[];
|
|
314
|
+
/**
|
|
315
|
+
* Create a test key (`gm_test_…`): same scopes and checks as a live key, but sends are
|
|
316
|
+
* never delivered. They get simulated events and webhooks and count toward nothing.
|
|
317
|
+
*/
|
|
318
|
+
test?: boolean;
|
|
319
|
+
/**
|
|
320
|
+
* Limit the key to one tenant (external id or Groundmail id): it sends only as that
|
|
321
|
+
* tenant and sees only that tenant's emails, domains and suppressions.
|
|
322
|
+
*/
|
|
323
|
+
tenant?: string;
|
|
280
324
|
};
|
|
281
325
|
type CreateApiKeyResult = {
|
|
282
326
|
id: string;
|
|
283
327
|
name: string;
|
|
284
328
|
scope: ApiKeyScope;
|
|
329
|
+
scopes?: string[];
|
|
330
|
+
allowed_domains?: string[];
|
|
331
|
+
/** A test key (`gm_test_…`). */
|
|
332
|
+
test?: boolean;
|
|
333
|
+
/** The tenant this key is limited to (external id), when it is. */
|
|
334
|
+
tenant?: string;
|
|
285
335
|
prefix: string;
|
|
286
336
|
token: string;
|
|
287
337
|
created_at: string;
|
|
@@ -326,6 +376,10 @@ type EmailWebhookEventPayload = {
|
|
|
326
376
|
bounce_reason?: string | null;
|
|
327
377
|
/** Set on `email.bounced` and `email.failed` when the email bounced: `hard` or `block`. */
|
|
328
378
|
bounce_type?: BounceType | null;
|
|
379
|
+
/** `true` on simulated events (test keys, simulator addresses, `POST /emails/test`). Absent on real mail. */
|
|
380
|
+
test?: true;
|
|
381
|
+
/** The tenant's external id, when the email was sent as a tenant. */
|
|
382
|
+
tenant?: string;
|
|
329
383
|
};
|
|
330
384
|
};
|
|
331
385
|
/** `contact.unsubscribed` webhook payload (`scope` distinguishes global vs category opt-out). */
|
|
@@ -356,10 +410,14 @@ type SuppressedErrorDetails = {
|
|
|
356
410
|
};
|
|
357
411
|
/**
|
|
358
412
|
* `details` on a 429 `daily_ramp_limit`: the send would go over a new account's
|
|
359
|
-
* daily ramp-up limit for its stream
|
|
413
|
+
* daily ramp-up limit for its stream, or over a tenant's own daily limit (then
|
|
414
|
+
* `tenant` is set). Send the rest after `resets_at`.
|
|
360
415
|
*/
|
|
361
416
|
type DailyRampLimitErrorDetails = {
|
|
362
|
-
|
|
417
|
+
/** Set when a tenant's own daily limit or ramp refused the send: the tenant's external id. */
|
|
418
|
+
tenant?: string;
|
|
419
|
+
/** The stream whose limit was reached. Absent when a tenant's fixed `daily_limit` was reached. */
|
|
420
|
+
stream?: 'transactional' | 'product';
|
|
363
421
|
/** Today's recipient limit for this stream. */
|
|
364
422
|
daily_limit: number;
|
|
365
423
|
/** Recipients already sent today on this stream. */
|
|
@@ -367,7 +425,7 @@ type DailyRampLimitErrorDetails = {
|
|
|
367
425
|
/** Recipients (to + cc + bcc) in the refused send. */
|
|
368
426
|
recipients: number;
|
|
369
427
|
/** Ramp-up stage, 1-based; null with a fixed limit set by support. */
|
|
370
|
-
ramp_stage
|
|
428
|
+
ramp_stage?: number | null;
|
|
371
429
|
/** The next 00:00 UTC, ISO 8601. */
|
|
372
430
|
resets_at: string;
|
|
373
431
|
};
|
|
@@ -381,6 +439,8 @@ type TestEmailWebhookParams = {
|
|
|
381
439
|
sync?: boolean;
|
|
382
440
|
/** When true (default), enqueue delivery to account webhooks. */
|
|
383
441
|
deliver?: boolean;
|
|
442
|
+
/** Put this tenant (external id or Groundmail id) on the test payload, to test tenant-filtered webhooks. */
|
|
443
|
+
tenant?: string;
|
|
384
444
|
};
|
|
385
445
|
type WebhookTestDelivery = {
|
|
386
446
|
endpoint_id: string;
|
|
@@ -532,17 +592,23 @@ type WebhookEndpoint = {
|
|
|
532
592
|
url: string;
|
|
533
593
|
events: string[];
|
|
534
594
|
enabled: boolean;
|
|
595
|
+
/** Present only when the endpoint receives email events for some tenants only (their external ids). */
|
|
596
|
+
tenants?: string[];
|
|
535
597
|
created_at: string;
|
|
536
598
|
updated_at: string;
|
|
537
599
|
};
|
|
538
600
|
type CreateWebhookParams = {
|
|
539
601
|
url: string;
|
|
540
602
|
events?: WebhookEventType[];
|
|
603
|
+
/** Only these tenants' email events (external ids or Groundmail ids). Omit, `[]` or `null` for all. */
|
|
604
|
+
tenants?: string[] | null;
|
|
541
605
|
};
|
|
542
606
|
type UpdateWebhookParams = {
|
|
543
607
|
url?: string;
|
|
544
608
|
events?: WebhookEventType[];
|
|
545
609
|
enabled?: boolean;
|
|
610
|
+
/** Only these tenants' email events. `[]` or `null` removes the filter. */
|
|
611
|
+
tenants?: string[] | null;
|
|
546
612
|
};
|
|
547
613
|
type WebhookEndpointWithSecret = {
|
|
548
614
|
webhook: WebhookEndpoint;
|
|
@@ -553,6 +619,8 @@ type Suppression = {
|
|
|
553
619
|
email: string;
|
|
554
620
|
reason: string | null;
|
|
555
621
|
source: string;
|
|
622
|
+
/** The tenant's external id, on an entry of a tenant's own list. */
|
|
623
|
+
tenant?: string;
|
|
556
624
|
created_at: string;
|
|
557
625
|
updated_at: string;
|
|
558
626
|
};
|
|
@@ -560,6 +628,8 @@ type ListSuppressionsParams = {
|
|
|
560
628
|
limit?: number;
|
|
561
629
|
cursor?: string;
|
|
562
630
|
email?: string;
|
|
631
|
+
/** List this tenant's own suppression list instead of the account's. */
|
|
632
|
+
tenant?: string;
|
|
563
633
|
};
|
|
564
634
|
type ListSuppressionsResult = {
|
|
565
635
|
data: Suppression[];
|
|
@@ -569,6 +639,8 @@ type ListSuppressionsResult = {
|
|
|
569
639
|
type CreateSuppressionParams = {
|
|
570
640
|
email: string;
|
|
571
641
|
reason?: string;
|
|
642
|
+
/** Add to this tenant's own list (external id or Groundmail id) instead of the account's. */
|
|
643
|
+
tenant?: string;
|
|
572
644
|
};
|
|
573
645
|
type TemplateStatus = 'draft' | 'published';
|
|
574
646
|
type TemplateFormat = 'html' | 'blocks';
|
|
@@ -724,6 +796,8 @@ type Domain = {
|
|
|
724
796
|
inbound_enabled: boolean;
|
|
725
797
|
inbound_status: 'disabled' | 'pending' | 'active' | 'error' | string;
|
|
726
798
|
inbound_error: string | null;
|
|
799
|
+
/** The tenant that owns this domain (external id). Absent for an account-wide domain. */
|
|
800
|
+
tenant?: string | null;
|
|
727
801
|
};
|
|
728
802
|
type DnsRecord = {
|
|
729
803
|
type: string;
|
|
@@ -737,12 +811,16 @@ type CreateDomainParams = {
|
|
|
737
811
|
name: string;
|
|
738
812
|
inbound_enabled?: boolean;
|
|
739
813
|
inboundEnabled?: boolean;
|
|
814
|
+
/** Give the new domain to this tenant (external id or Groundmail id). Only that tenant can send from it. */
|
|
815
|
+
tenant?: string;
|
|
740
816
|
};
|
|
741
817
|
type ListDomainsParams = {
|
|
742
818
|
limit?: number;
|
|
743
819
|
cursor?: string;
|
|
744
820
|
inbound_enabled?: boolean;
|
|
745
821
|
inboundEnabled?: boolean;
|
|
822
|
+
/** Only this tenant's domains. */
|
|
823
|
+
tenant?: string;
|
|
746
824
|
};
|
|
747
825
|
type ListDomainsResult = {
|
|
748
826
|
domains: Domain[];
|
|
@@ -1008,6 +1086,117 @@ type RunMigrationOptions = {
|
|
|
1008
1086
|
/** Waits for `retry_after_seconds` (tests can pass a no-op). */
|
|
1009
1087
|
sleep?: (ms: number) => Promise<void>;
|
|
1010
1088
|
};
|
|
1089
|
+
type TenantStatus = 'active' | 'paused';
|
|
1090
|
+
/** `all` (default) or `transactional_only` (product email is refused for this tenant). */
|
|
1091
|
+
type TenantStreamPolicy = 'all' | 'transactional_only';
|
|
1092
|
+
/** Who paused a tenant: you over the API, Groundmail support, or Groundmail's automatic checks. */
|
|
1093
|
+
type TenantPauseSource = 'api' | 'admin' | 'automated';
|
|
1094
|
+
/**
|
|
1095
|
+
* One of your customers, inside your Groundmail account. Each tenant has its own
|
|
1096
|
+
* domains, suppression list, stats, pause switch and daily limit.
|
|
1097
|
+
*/
|
|
1098
|
+
type Tenant = {
|
|
1099
|
+
id: string;
|
|
1100
|
+
object: 'tenant';
|
|
1101
|
+
/** Your id for this customer. Unique in your account; cannot be changed. */
|
|
1102
|
+
external_id: string;
|
|
1103
|
+
name: string | null;
|
|
1104
|
+
status: TenantStatus;
|
|
1105
|
+
paused_reason: string | null;
|
|
1106
|
+
paused_at: string | null;
|
|
1107
|
+
pause_source: TenantPauseSource | null;
|
|
1108
|
+
/** Completes a bare `from` (`"billing"` → `billing@<domain>`) on sends that name this tenant. */
|
|
1109
|
+
default_from_domain: string | null;
|
|
1110
|
+
stream_policy: TenantStreamPolicy;
|
|
1111
|
+
/** Most recipients per UTC day for this tenant; null for no tenant limit. */
|
|
1112
|
+
daily_limit: number | null;
|
|
1113
|
+
metadata: Record<string, unknown> | null;
|
|
1114
|
+
created_at: string;
|
|
1115
|
+
updated_at: string;
|
|
1116
|
+
};
|
|
1117
|
+
type ListTenantsParams = {
|
|
1118
|
+
limit?: number;
|
|
1119
|
+
cursor?: string;
|
|
1120
|
+
status?: TenantStatus;
|
|
1121
|
+
};
|
|
1122
|
+
type ListTenantsResult = {
|
|
1123
|
+
data: Tenant[];
|
|
1124
|
+
has_more: boolean;
|
|
1125
|
+
next_cursor: string | null;
|
|
1126
|
+
};
|
|
1127
|
+
type CreateTenantParams = {
|
|
1128
|
+
/** Your id for this customer: 1–128 characters of `A-Z a-z 0-9 . _ : @ -`. */
|
|
1129
|
+
external_id: string;
|
|
1130
|
+
name?: string;
|
|
1131
|
+
default_from_domain?: string | null;
|
|
1132
|
+
stream_policy?: TenantStreamPolicy;
|
|
1133
|
+
daily_limit?: number | null;
|
|
1134
|
+
metadata?: Record<string, unknown> | null;
|
|
1135
|
+
};
|
|
1136
|
+
type UpdateTenantParams = {
|
|
1137
|
+
name?: string | null;
|
|
1138
|
+
default_from_domain?: string | null;
|
|
1139
|
+
stream_policy?: TenantStreamPolicy;
|
|
1140
|
+
daily_limit?: number | null;
|
|
1141
|
+
metadata?: Record<string, unknown> | null;
|
|
1142
|
+
/** `paused` stops this tenant's new sends; `active` resumes them. */
|
|
1143
|
+
status?: TenantStatus;
|
|
1144
|
+
/** Why you paused the tenant (with `status: 'paused'`). */
|
|
1145
|
+
reason?: string;
|
|
1146
|
+
};
|
|
1147
|
+
type DeleteTenantResult = {
|
|
1148
|
+
ok: boolean;
|
|
1149
|
+
id: string;
|
|
1150
|
+
external_id: string;
|
|
1151
|
+
deleted: true;
|
|
1152
|
+
};
|
|
1153
|
+
/** A tenant's email counts over the last `days`. Rates are 0–1. */
|
|
1154
|
+
type TenantStats = {
|
|
1155
|
+
object: 'tenant_stats';
|
|
1156
|
+
days: number;
|
|
1157
|
+
/** Start of the window, ISO 8601. */
|
|
1158
|
+
from: string;
|
|
1159
|
+
total: number;
|
|
1160
|
+
queued: number;
|
|
1161
|
+
scheduled: number;
|
|
1162
|
+
sent: number;
|
|
1163
|
+
delivered: number;
|
|
1164
|
+
bounced: number;
|
|
1165
|
+
complained: number;
|
|
1166
|
+
failed: number;
|
|
1167
|
+
suppressed: number;
|
|
1168
|
+
opened: number;
|
|
1169
|
+
clicked: number;
|
|
1170
|
+
bounce_rate: number;
|
|
1171
|
+
complaint_rate: number;
|
|
1172
|
+
};
|
|
1173
|
+
/** A tenant's sending ramp today. `active` is false when no ramp limit applies. */
|
|
1174
|
+
type TenantRamp = {
|
|
1175
|
+
object: 'tenant_ramp';
|
|
1176
|
+
active: boolean;
|
|
1177
|
+
/** 1-based stage; null when the ramp does not apply. */
|
|
1178
|
+
stage: number | null;
|
|
1179
|
+
daily_limit: {
|
|
1180
|
+
transactional: number | null;
|
|
1181
|
+
product: number | null;
|
|
1182
|
+
} | null;
|
|
1183
|
+
used_today: {
|
|
1184
|
+
transactional: number;
|
|
1185
|
+
product: number;
|
|
1186
|
+
};
|
|
1187
|
+
completed: boolean;
|
|
1188
|
+
/** The next 00:00 UTC, ISO 8601. */
|
|
1189
|
+
resets_at: string;
|
|
1190
|
+
};
|
|
1191
|
+
type TenantStatsResult = {
|
|
1192
|
+
/** The tenant's external id. */
|
|
1193
|
+
tenant: string;
|
|
1194
|
+
stats: TenantStats;
|
|
1195
|
+
ramp?: TenantRamp;
|
|
1196
|
+
};
|
|
1197
|
+
type ListTenantDomainsResult = {
|
|
1198
|
+
data: Domain[];
|
|
1199
|
+
};
|
|
1011
1200
|
|
|
1012
1201
|
declare class AutomationsResource {
|
|
1013
1202
|
private readonly client;
|
|
@@ -1033,6 +1222,121 @@ declare class AutomationsResource {
|
|
|
1033
1222
|
retryRun(id: string): Promise<AutomationRunResponse>;
|
|
1034
1223
|
}
|
|
1035
1224
|
|
|
1225
|
+
/** Status of one sending source in the DMARC reports. `unknown`: an unrecognised server failing DMARC (possible spoofing). */
|
|
1226
|
+
type DmarcSourceStatus = 'passing' | 'failing' | 'unknown';
|
|
1227
|
+
type DmarcSource = {
|
|
1228
|
+
/** Provider id (`google`, `microsoft`, `groundmail`, `sendgrid`…) or `unknown:<reverse DNS domain>`. */
|
|
1229
|
+
key: string;
|
|
1230
|
+
provider: string;
|
|
1231
|
+
name: string;
|
|
1232
|
+
status: DmarcSourceStatus;
|
|
1233
|
+
messages: number;
|
|
1234
|
+
dmarc_pass: number;
|
|
1235
|
+
/** 0–1. */
|
|
1236
|
+
pass_rate: number;
|
|
1237
|
+
spf_aligned_rate: number;
|
|
1238
|
+
dkim_aligned_rate: number;
|
|
1239
|
+
/** Up to five source IPs, busiest first. */
|
|
1240
|
+
ips: string[];
|
|
1241
|
+
ip_count: number;
|
|
1242
|
+
first_seen: string;
|
|
1243
|
+
last_seen: string;
|
|
1244
|
+
};
|
|
1245
|
+
type DmarcReadinessVerdict = 'no_data' | 'collecting' | 'fix_sources' | 'ready_quarantine' | 'ready_reject' | 'enforced';
|
|
1246
|
+
type DmarcReadiness = {
|
|
1247
|
+
verdict: DmarcReadinessVerdict;
|
|
1248
|
+
label: string;
|
|
1249
|
+
detail: string;
|
|
1250
|
+
days_of_data: number;
|
|
1251
|
+
blocking_sources: string[];
|
|
1252
|
+
};
|
|
1253
|
+
type DmarcDailyPoint = {
|
|
1254
|
+
date: string;
|
|
1255
|
+
messages: number;
|
|
1256
|
+
dmarc_pass: number;
|
|
1257
|
+
pass_rate: number;
|
|
1258
|
+
};
|
|
1259
|
+
type DmarcMonitoring = {
|
|
1260
|
+
domain: string;
|
|
1261
|
+
enabled: true;
|
|
1262
|
+
/** `waiting` until the first report arrives. */
|
|
1263
|
+
status: 'waiting' | 'receiving';
|
|
1264
|
+
/** Where receivers send the domain's reports. */
|
|
1265
|
+
report_address: string;
|
|
1266
|
+
/** The `rua=` value: `mailto:<report_address>`. */
|
|
1267
|
+
rua: string;
|
|
1268
|
+
dns: {
|
|
1269
|
+
host: string;
|
|
1270
|
+
record: string | null;
|
|
1271
|
+
rua_present: boolean;
|
|
1272
|
+
policy: string | null;
|
|
1273
|
+
/** The current record with our address added to rua=. Publish this. */
|
|
1274
|
+
suggested_record: string;
|
|
1275
|
+
};
|
|
1276
|
+
digest_enabled: boolean;
|
|
1277
|
+
created_at: string;
|
|
1278
|
+
last_report_at: string | null;
|
|
1279
|
+
window_days: number;
|
|
1280
|
+
summary: {
|
|
1281
|
+
messages: number;
|
|
1282
|
+
dmarc_pass: number;
|
|
1283
|
+
dmarc_fail: number;
|
|
1284
|
+
pass_rate: number;
|
|
1285
|
+
reports: number;
|
|
1286
|
+
reporters: string[];
|
|
1287
|
+
};
|
|
1288
|
+
sources: DmarcSource[];
|
|
1289
|
+
daily: DmarcDailyPoint[];
|
|
1290
|
+
readiness: DmarcReadiness;
|
|
1291
|
+
};
|
|
1292
|
+
type DmarcMonitoringDisabled = {
|
|
1293
|
+
domain: string;
|
|
1294
|
+
enabled: false;
|
|
1295
|
+
status: 'disabled';
|
|
1296
|
+
dns: {
|
|
1297
|
+
host: string;
|
|
1298
|
+
record: string | null;
|
|
1299
|
+
policy: string | null;
|
|
1300
|
+
};
|
|
1301
|
+
};
|
|
1302
|
+
type DmarcStatus = DmarcMonitoring | DmarcMonitoringDisabled;
|
|
1303
|
+
type EnableDmarcParams = {
|
|
1304
|
+
/** Also write the record through a DNS provider connected under Settings → Integrations. */
|
|
1305
|
+
apply?: 'cloudflare' | 'vercel' | 'godaddy';
|
|
1306
|
+
digest_enabled?: boolean;
|
|
1307
|
+
};
|
|
1308
|
+
type EnableDmarcResult = {
|
|
1309
|
+
dmarc: DmarcMonitoring;
|
|
1310
|
+
applied: {
|
|
1311
|
+
provider: string;
|
|
1312
|
+
results: unknown;
|
|
1313
|
+
} | null;
|
|
1314
|
+
apply_error?: string;
|
|
1315
|
+
};
|
|
1316
|
+
/** DMARC monitoring for a sending domain: reports, sources, pass rates and enforcement readiness. */
|
|
1317
|
+
declare class DmarcResource {
|
|
1318
|
+
private readonly client;
|
|
1319
|
+
constructor(client: Groundmail);
|
|
1320
|
+
private path;
|
|
1321
|
+
/** Monitoring status and the last 30 days of reports. `domain` is the domain id or name. */
|
|
1322
|
+
get(domain: string): Promise<{
|
|
1323
|
+
dmarc: DmarcStatus;
|
|
1324
|
+
}>;
|
|
1325
|
+
/** Turn monitoring on. Returns the report address and the DMARC record to publish. */
|
|
1326
|
+
enable(domain: string, params?: EnableDmarcParams): Promise<EnableDmarcResult>;
|
|
1327
|
+
/** Turn the weekly digest email on or off. */
|
|
1328
|
+
update(domain: string, params: {
|
|
1329
|
+
digest_enabled: boolean;
|
|
1330
|
+
}): Promise<{
|
|
1331
|
+
dmarc: DmarcStatus;
|
|
1332
|
+
}>;
|
|
1333
|
+
/** Stop monitoring. Remove the address from your DMARC record's rua= tag too. */
|
|
1334
|
+
disable(domain: string): Promise<{
|
|
1335
|
+
dmarc: DmarcStatus;
|
|
1336
|
+
disabled: boolean;
|
|
1337
|
+
}>;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1036
1340
|
declare class DomainsResource {
|
|
1037
1341
|
private readonly client;
|
|
1038
1342
|
constructor(client: Groundmail);
|
|
@@ -1157,8 +1461,13 @@ declare class SuppressionsResource {
|
|
|
1157
1461
|
create(params: CreateSuppressionParams): Promise<{
|
|
1158
1462
|
suppression: Suppression;
|
|
1159
1463
|
}>;
|
|
1160
|
-
/**
|
|
1161
|
-
|
|
1464
|
+
/**
|
|
1465
|
+
* Remove by suppression id or email address. Pass `tenant` to remove an address
|
|
1466
|
+
* from that tenant's own list (by email address).
|
|
1467
|
+
*/
|
|
1468
|
+
remove(idOrEmail: string, options?: {
|
|
1469
|
+
tenant?: string;
|
|
1470
|
+
}): Promise<{
|
|
1162
1471
|
ok: boolean;
|
|
1163
1472
|
}>;
|
|
1164
1473
|
}
|
|
@@ -1207,6 +1516,61 @@ declare class TemplatesResource {
|
|
|
1207
1516
|
}): Promise<unknown>;
|
|
1208
1517
|
}
|
|
1209
1518
|
|
|
1519
|
+
/**
|
|
1520
|
+
* Tenants: one per customer of your platform, each with its own domains,
|
|
1521
|
+
* suppression list, stats, pause switch and daily limit. Every method takes the
|
|
1522
|
+
* tenant's external id (your id for the customer) or its Groundmail id.
|
|
1523
|
+
*/
|
|
1524
|
+
declare class TenantsResource {
|
|
1525
|
+
private readonly client;
|
|
1526
|
+
/** The domains a tenant owns. */
|
|
1527
|
+
readonly domains: TenantDomainsResource;
|
|
1528
|
+
constructor(client: Groundmail);
|
|
1529
|
+
list(params?: ListTenantsParams): Promise<ListTenantsResult>;
|
|
1530
|
+
create(params: CreateTenantParams): Promise<{
|
|
1531
|
+
tenant: Tenant;
|
|
1532
|
+
}>;
|
|
1533
|
+
get(tenant: string): Promise<{
|
|
1534
|
+
tenant: Tenant;
|
|
1535
|
+
}>;
|
|
1536
|
+
update(tenant: string, params: UpdateTenantParams): Promise<{
|
|
1537
|
+
tenant: Tenant;
|
|
1538
|
+
}>;
|
|
1539
|
+
/** Stop the tenant's new sends until you resume it. */
|
|
1540
|
+
pause(tenant: string, reason?: string): Promise<{
|
|
1541
|
+
tenant: Tenant;
|
|
1542
|
+
}>;
|
|
1543
|
+
/**
|
|
1544
|
+
* Let a tenant you paused send again. A tenant Groundmail paused (`pause_source`
|
|
1545
|
+
* `automated` or `admin`) cannot be resumed this way: contact support.
|
|
1546
|
+
*/
|
|
1547
|
+
resume(tenant: string): Promise<{
|
|
1548
|
+
tenant: Tenant;
|
|
1549
|
+
}>;
|
|
1550
|
+
/**
|
|
1551
|
+
* Delete a tenant and its suppression list. Its domains go back to the account,
|
|
1552
|
+
* its emails keep their history, and API keys limited to it stop working.
|
|
1553
|
+
*/
|
|
1554
|
+
delete(tenant: string): Promise<DeleteTenantResult>;
|
|
1555
|
+
/** Email counts and rates over the last `days` (1–90, default 7), plus the tenant's ramp today. */
|
|
1556
|
+
stats(tenant: string, params?: {
|
|
1557
|
+
days?: number;
|
|
1558
|
+
}): Promise<TenantStatsResult>;
|
|
1559
|
+
}
|
|
1560
|
+
declare class TenantDomainsResource {
|
|
1561
|
+
private readonly client;
|
|
1562
|
+
constructor(client: Groundmail);
|
|
1563
|
+
list(tenant: string): Promise<ListTenantDomainsResult>;
|
|
1564
|
+
/** Give an existing domain of the account (name or id) to the tenant. Only that tenant can then send from it. */
|
|
1565
|
+
assign(tenant: string, domain: string): Promise<{
|
|
1566
|
+
domain: Domain;
|
|
1567
|
+
}>;
|
|
1568
|
+
/** Give the tenant's domain (name or id) back to the whole account. The domain itself stays. */
|
|
1569
|
+
unassign(tenant: string, domain: string): Promise<{
|
|
1570
|
+
domain: Domain;
|
|
1571
|
+
}>;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1210
1574
|
declare class WebhooksResource {
|
|
1211
1575
|
private readonly client;
|
|
1212
1576
|
constructor(client: Groundmail);
|
|
@@ -1247,8 +1611,12 @@ declare class Groundmail {
|
|
|
1247
1611
|
readonly smtpCredentials: SmtpCredentialsResource;
|
|
1248
1612
|
readonly suppressions: SuppressionsResource;
|
|
1249
1613
|
readonly templates: TemplatesResource;
|
|
1614
|
+
/** Your platform's customers, each with its own domains, suppressions, stats and limits. */
|
|
1615
|
+
readonly tenants: TenantsResource;
|
|
1250
1616
|
/** Import from SendGrid, Postmark or Mailgun (Beta). */
|
|
1251
1617
|
readonly migrations: MigrationsResource;
|
|
1618
|
+
/** DMARC monitoring per domain: report address, sources, pass rates and readiness. */
|
|
1619
|
+
readonly dmarc: DmarcResource;
|
|
1252
1620
|
constructor(apiKey: string, options?: GroundmailOptions);
|
|
1253
1621
|
/**
|
|
1254
1622
|
* Check one address with Groundmail validation before you accept it (for
|
|
@@ -1263,4 +1631,4 @@ declare class Groundmail {
|
|
|
1263
1631
|
request<T>(path: string, init: RequestInit): Promise<T>;
|
|
1264
1632
|
}
|
|
1265
1633
|
|
|
1266
|
-
export { type
|
|
1634
|
+
export { type Domain 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 CreateTenantParams as H, type CreateWebhookParams as I, DEFAULT_API_BASE_URL as J, type DailyRampLimitErrorDetails as K, type DeleteTenantResult as L, type DeliverabilityBucket as M, type DeliverabilityDomainInsight as N, type DeliverabilityInsightsResult as O, type DeliverabilityMailServerRelay as P, type DeliverabilityWindow as Q, type DmarcDailyPoint as R, type SendEmailResult as S, type DmarcMonitoring as T, type DmarcMonitoringDisabled as U, type DmarcReadiness as V, type DmarcReadinessVerdict as W, type DmarcSource as X, type DmarcSourceStatus as Y, type DmarcStatus as Z, type DnsProviderName as _, type ApiErrorCode as a, type Tenant as a$, type DomainBimiAnalysis as a0, type DomainDetailResult as a1, type DomainDmarcAnalysis as a2, type DomainDnsAnalysis as a3, type DomainStatus as a4, type DomainTlsMode as a5, type DomainTrackingDns as a6, type DomainTrackingStatus as a7, type EmailListItem as a8, type EmailWebhookEventPayload as a9, type ListTenantsResult as aA, type MailServerSigningStatus as aB, type Migration as aC, type MigrationDnsChange as aD, type MigrationDnsPlan as aE, type MigrationDomainItem as aF, type MigrationItemStatus as aG, type MigrationProvider as aH, type MigrationResource as aI, type MigrationSummary as aJ, type MigrationTemplateItem as aK, type MigrationTotals as aL, type MigrationWebhookItem as aM, type ReceivedEmail as aN, type ReceivedEmailAttachment as aO, type ReceivedEmailAttachmentMeta as aP, type RemoteApplyDnsCredentials as aQ, type RunMigrationOptions as aR, type SendEmailParams as aS, type SuppressedErrorDetails as aT, type Suppression as aU, type Template as aV, type TemplateFormat as aW, type TemplateLintWarning as aX, type TemplateStarter as aY, type TemplateStatus as aZ, type TemplateVersion as a_, type EmailWebhookEventType as aa, type EnableDmarcParams as ab, type EnableDmarcResult as ac, type EnrollmentSkip as ad, type EventRecord as ae, type GroundmailOptions as af, type HeldBackErrorDetails as ag, type HeldBackReason as ah, type HeldBackRecipient as ai, type ListApiKeysParams as aj, type ListApiKeysResult as ak, type ListAutomationRunsParams as al, type ListAutomationRunsResult as am, type ListAutomationsResult as an, type ListDomainsParams as ao, type ListDomainsResult as ap, type ListEmailsParams as aq, type ListEmailsResult as ar, type ListReceivedEmailsParams as as, type ListReceivedEmailsResult as at, type ListSuppressionsParams as au, type ListSuppressionsResult as av, type ListTemplatesParams as aw, type ListTemplatesResult as ax, type ListTenantDomainsResult as ay, type ListTenantsParams as az, type ApiKey as b, type TenantPauseSource as b0, type TenantRamp as b1, type TenantStats as b2, type TenantStatsResult as b3, type TenantStatus as b4, type TenantStreamPolicy as b5, type TestEmailWebhookParams as b6, type TestEmailWebhookResult as b7, type TriggerEventParams as b8, type TriggerEventResult as b9, type TwilioIntegrationResponse as ba, type TwilioIntegrationStatus as bb, type UpdateAutomationParams as bc, type UpdateDomainParams as bd, type UpdateEmailParams as be, type UpdateTemplateParams as bf, type UpdateTenantParams as bg, type UpdateWebhookParams as bh, type ValidateEmailResult as bi, type ValidationReason as bj, type ValidationVerdict as bk, type VerifyDomainResult as bl, type WebhookEndpoint as bm, type WebhookEndpointWithSecret as bn, type WebhookEventType as bo, mergeMigrations as bp, 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-
|
|
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
|
|
1
|
+
import { A as ApiErrorBody, D as DnsRecord } from './client-H0RF4KEK.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 CreateTenantParams, I as CreateWebhookParams, J as DEFAULT_API_BASE_URL, K as DailyRampLimitErrorDetails, L as DeleteTenantResult, M as DeliverabilityBucket, N as DeliverabilityDomainInsight, O as DeliverabilityInsightsResult, P as DeliverabilityMailServerRelay, Q as DeliverabilityWindow, R as DmarcDailyPoint, T as DmarcMonitoring, U as DmarcMonitoringDisabled, V as DmarcReadiness, W as DmarcReadinessVerdict, X as DmarcSource, Y as DmarcSourceStatus, Z as DmarcStatus, _ as DnsProviderName, $ as Domain, a0 as DomainBimiAnalysis, a1 as DomainDetailResult, a2 as DomainDmarcAnalysis, a3 as DomainDnsAnalysis, a4 as DomainStatus, a5 as DomainTlsMode, a6 as DomainTrackingDns, a7 as DomainTrackingStatus, a8 as EmailListItem, a9 as EmailWebhookEventPayload, aa as EmailWebhookEventType, ab as EnableDmarcParams, ac as EnableDmarcResult, ad as EnrollmentSkip, ae as EventRecord, G as Groundmail, af as GroundmailOptions, ag as HeldBackErrorDetails, ah as HeldBackReason, ai as HeldBackRecipient, aj as ListApiKeysParams, ak as ListApiKeysResult, al as ListAutomationRunsParams, am as ListAutomationRunsResult, an as ListAutomationsResult, ao as ListDomainsParams, ap as ListDomainsResult, aq as ListEmailsParams, ar as ListEmailsResult, as as ListReceivedEmailsParams, at as ListReceivedEmailsResult, au as ListSuppressionsParams, av as ListSuppressionsResult, aw as ListTemplatesParams, ax as ListTemplatesResult, ay as ListTenantDomainsResult, az as ListTenantsParams, aA as ListTenantsResult, aB as MailServerSigningStatus, aC as Migration, aD as MigrationDnsChange, aE as MigrationDnsPlan, aF as MigrationDomainItem, aG as MigrationItemStatus, aH as MigrationProvider, aI as MigrationResource, aJ as MigrationSummary, aK as MigrationTemplateItem, aL as MigrationTotals, aM as MigrationWebhookItem, aN as ReceivedEmail, aO as ReceivedEmailAttachment, aP as ReceivedEmailAttachmentMeta, aQ as RemoteApplyDnsCredentials, aR as RunMigrationOptions, aS as SendEmailParams, S as SendEmailResult, aT as SuppressedErrorDetails, aU as Suppression, aV as Template, aW as TemplateFormat, aX as TemplateLintWarning, aY as TemplateStarter, aZ as TemplateStatus, a_ as TemplateVersion, a$ as Tenant, b0 as TenantPauseSource, b1 as TenantRamp, b2 as TenantStats, b3 as TenantStatsResult, b4 as TenantStatus, b5 as TenantStreamPolicy, b6 as TestEmailWebhookParams, b7 as TestEmailWebhookResult, b8 as TriggerEventParams, b9 as TriggerEventResult, ba as TwilioIntegrationResponse, bb as TwilioIntegrationStatus, bc as UpdateAutomationParams, bd as UpdateDomainParams, be as UpdateEmailParams, bf as UpdateTemplateParams, bg as UpdateTenantParams, bh as UpdateWebhookParams, bi as ValidateEmailResult, bj as ValidationReason, bk as ValidationVerdict, bl as VerifyDomainResult, bm as WebhookEndpoint, bn as WebhookEndpointWithSecret, bo as WebhookEventType, bp as mergeMigrations } from './client-H0RF4KEK.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-
|
|
11
|
+
} from "./chunk-2RKWN4OE.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
package/dist/payload.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groundmail",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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.
|
|
92
|
+
"groundmail-nodemailer": "0.15.5"
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
95
|
"@types/nodemailer": "^8.0.1",
|