groundmail 0.26.1 → 0.27.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/dist/authjs.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { G as Groundmail } from './client-DZNmwhee.js';
1
+ import { G as Groundmail } from './client-Btlt7wMg.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-E6G7G2PM.js";
4
+ } from "./chunk-RLIF6XKO.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-DZNmwhee.js';
1
+ import { S as SendEmailResult, G as Groundmail } from './client-Btlt7wMg.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-E6G7G2PM.js";
4
+ } from "./chunk-RLIF6XKO.js";
5
5
 
6
6
  // src/better-auth.ts
7
7
  function linkHtml(label, url) {
@@ -71,9 +71,9 @@ function isSpfRecord(value) {
71
71
  }
72
72
 
73
73
  // src/dns/cloudflare.ts
74
- function relativeName(fqdn, zone) {
74
+ function relativeName(fqdn2, zone) {
75
75
  const domain = zone.replace(/\.$/, "").toLowerCase();
76
- const host = fqdn.replace(/\.$/, "").toLowerCase();
76
+ const host = fqdn2.replace(/\.$/, "").toLowerCase();
77
77
  if (host === domain) return "@";
78
78
  if (host.endsWith(`.${domain}`)) return host.slice(0, -(domain.length + 1));
79
79
  return host;
@@ -302,9 +302,9 @@ function createCloudflareProvider(options) {
302
302
  }
303
303
 
304
304
  // src/dns/godaddy.ts
305
- function relativeName2(fqdn, zone) {
305
+ function relativeName2(fqdn2, zone) {
306
306
  const domain = zone.replace(/\.$/, "").toLowerCase();
307
- const host = fqdn.replace(/\.$/, "").toLowerCase();
307
+ const host = fqdn2.replace(/\.$/, "").toLowerCase();
308
308
  if (host === domain) return "@";
309
309
  if (host.endsWith(`.${domain}`)) return host.slice(0, -(domain.length + 1));
310
310
  return host;
@@ -471,7 +471,7 @@ function createGoDaddyProvider(options) {
471
471
  };
472
472
  }
473
473
 
474
- // src/dns/vercel.ts
474
+ // src/dns/record-store.ts
475
475
  function zoneNameCandidates2(domain) {
476
476
  const labels = domain.replace(/\.$/, "").toLowerCase().split(".").filter(Boolean);
477
477
  const candidates = [];
@@ -480,9 +480,296 @@ function zoneNameCandidates2(domain) {
480
480
  }
481
481
  return candidates;
482
482
  }
483
- function relativeName3(fqdn, zone) {
483
+ function relativeName3(fqdn2, zone) {
484
484
  const domain = zone.replace(/\.$/, "").toLowerCase();
485
- const host = fqdn.replace(/\.$/, "").toLowerCase();
485
+ const host = fqdn2.replace(/\.$/, "").toLowerCase();
486
+ if (host === domain) return "";
487
+ if (host.endsWith(`.${domain}`)) return host.slice(0, -(domain.length + 1));
488
+ return host;
489
+ }
490
+ function fqdn(name, zone) {
491
+ return name ? `${name}.${zone}` : zone;
492
+ }
493
+ function normalizeValue(type, value) {
494
+ const trimmed = value.trim();
495
+ if (type === "TXT") return trimmed.replace(/^"([\s\S]*)"$/, "$1").replace(/"\s+"/g, "");
496
+ return trimmed.replace(/\.$/, "").toLowerCase();
497
+ }
498
+ function ownedTxt(purpose, value) {
499
+ const lower = value.toLowerCase();
500
+ if (purpose === "Domain ownership") return lower.includes("groundmail-verify=");
501
+ if (purpose === "DKIM") return lower.includes("v=dkim1");
502
+ if (purpose === "DMARC") return lower.includes("v=dmarc1");
503
+ return false;
504
+ }
505
+ async function applyToRecordStore(zone, records, store) {
506
+ const results = [];
507
+ let cached = await store.list();
508
+ const at = (type, name) => cached.filter((row) => row.type.toUpperCase() === type && row.name === name);
509
+ for (const record of records) {
510
+ const type = record.type.toUpperCase();
511
+ const name = relativeName3(record.host, zone);
512
+ const value = normalizeValue(type, record.value);
513
+ const done = (action, detail) => results.push({ purpose: record.purpose, host: record.host, action, ...detail ? { detail } : {} });
514
+ if (type === "CNAME") {
515
+ const existing = at("CNAME", name)[0];
516
+ if (existing && normalizeValue(type, existing.value) === value) {
517
+ done("unchanged");
518
+ continue;
519
+ }
520
+ if (existing) {
521
+ await store.update(existing, { name, type, value });
522
+ done("updated");
523
+ } else {
524
+ await store.create({ name, type, value });
525
+ done("created");
526
+ }
527
+ } else if (type === "MX") {
528
+ const priority = record.priority ?? 10;
529
+ const sameName = at("MX", name);
530
+ if (sameName.some((row) => normalizeValue(type, row.value) === value && (row.priority ?? 10) === priority)) {
531
+ done("unchanged");
532
+ continue;
533
+ }
534
+ await store.create({ name, type, value, priority });
535
+ done(sameName.length > 0 ? "merged" : "created");
536
+ } else if (type !== "TXT") {
537
+ done("unchanged", `Skipped unsupported type ${record.type}`);
538
+ continue;
539
+ } else if (record.purpose === "SPF") {
540
+ const existing = at("TXT", name).find((row) => isSpfRecord(normalizeValue(type, row.value)));
541
+ const includeHost = record.value.match(/include:([^\s"]+)/i)?.[1] || "spf.groundmail.dev";
542
+ const current = existing ? normalizeValue(type, existing.value) : void 0;
543
+ const merged = mergeSpfInclude(current, includeHost);
544
+ if (existing && current?.trim() === merged.trim()) {
545
+ done("unchanged", "SPF already includes Groundmail");
546
+ continue;
547
+ }
548
+ if (existing) {
549
+ await store.update(existing, { name, type, value: merged });
550
+ done("merged", merged);
551
+ } else {
552
+ await store.create({ name, type, value: merged });
553
+ done("created", merged);
554
+ }
555
+ } else {
556
+ const sameName = at("TXT", name);
557
+ if (sameName.some((row) => normalizeValue(type, row.value) === value)) {
558
+ done("unchanged");
559
+ continue;
560
+ }
561
+ const replaceable = sameName.find((row) => ownedTxt(record.purpose, normalizeValue(type, row.value)));
562
+ if (replaceable) {
563
+ await store.update(replaceable, { name, type, value });
564
+ done("updated");
565
+ } else {
566
+ await store.create({ name, type, value });
567
+ done("created");
568
+ }
569
+ }
570
+ cached = await store.list();
571
+ }
572
+ return results;
573
+ }
574
+ async function resolveZone(domain, hostLabel, lookup) {
575
+ for (const candidate of zoneNameCandidates2(domain)) {
576
+ const info = await lookup(candidate);
577
+ if (info !== null) return { zone: candidate, info };
578
+ }
579
+ throw new Error(`No ${hostLabel} DNS zone found for ${domain}. The domain must use ${hostLabel} nameservers.`);
580
+ }
581
+
582
+ // src/dns/digitalocean.ts
583
+ function createDigitalOceanProvider(options) {
584
+ const fetchImpl = options.fetchImpl || fetch;
585
+ async function digitalocean(path, init) {
586
+ const response = await fetchImpl(`https://api.digitalocean.com/v2${path}`, {
587
+ ...init,
588
+ headers: { Authorization: `Bearer ${options.apiToken}`, "Content-Type": "application/json", ...init?.headers || {} }
589
+ });
590
+ const body = await response.json().catch(() => ({}));
591
+ if (!response.ok && response.status !== 404) {
592
+ if (response.status === 403) {
593
+ throw new Error("DigitalOcean refused the DNS change. Reconnect DigitalOcean under Integrations to allow DNS.");
594
+ }
595
+ throw new Error(body.message || `DigitalOcean HTTP ${response.status}`);
596
+ }
597
+ return { status: response.status, body };
598
+ }
599
+ return {
600
+ name: "digitalocean",
601
+ async apply(domain, records) {
602
+ const { zone } = await resolveZone(domain, "DigitalOcean", async (candidate) => {
603
+ const { status } = await digitalocean(`/domains/${encodeURIComponent(candidate)}`);
604
+ return status === 404 ? null : true;
605
+ });
606
+ const path = `/domains/${encodeURIComponent(zone)}/records`;
607
+ const body = (record) => ({
608
+ type: record.type,
609
+ name: record.name || "@",
610
+ data: record.type === "TXT" ? record.value : `${record.value}.`,
611
+ ttl: 1800,
612
+ ...record.priority != null ? { priority: record.priority } : {}
613
+ });
614
+ const write = async (method, suffix, record) => {
615
+ const { status } = await digitalocean(`${path}${suffix}`, { method, body: JSON.stringify(body(record)) });
616
+ if (status === 404) throw new Error(`DigitalOcean lost the ${zone} zone mid-update`);
617
+ };
618
+ return applyToRecordStore(zone, records, {
619
+ async list() {
620
+ const rows = [];
621
+ for (let page = 1; page <= 20; page++) {
622
+ const { body: list } = await digitalocean(`${path}?per_page=200&page=${page}`);
623
+ rows.push(...list.domain_records || []);
624
+ if (!list.links?.pages?.next) break;
625
+ }
626
+ return rows.map((row) => ({
627
+ id: String(row.id),
628
+ name: row.name === "@" ? "" : row.name.toLowerCase(),
629
+ type: row.type,
630
+ value: row.data,
631
+ priority: row.priority ?? void 0
632
+ }));
633
+ },
634
+ create: (record) => write("POST", "", record),
635
+ update: (existing, record) => write("PUT", `/${existing.id}`, record)
636
+ });
637
+ }
638
+ };
639
+ }
640
+
641
+ // src/dns/dnsimple.ts
642
+ function createDnsimpleProvider(options) {
643
+ const fetchImpl = options.fetchImpl || fetch;
644
+ const base = `https://api.dnsimple.com/v2/${encodeURIComponent(options.accountId)}`;
645
+ async function dnsimple(path, init) {
646
+ const response = await fetchImpl(`${base}${path}`, {
647
+ ...init,
648
+ headers: {
649
+ Authorization: `Bearer ${options.apiToken}`,
650
+ "Content-Type": "application/json",
651
+ Accept: "application/json",
652
+ ...init?.headers || {}
653
+ }
654
+ });
655
+ const body = await response.json().catch(() => ({}));
656
+ if (!response.ok && response.status !== 404) throw new Error(body.message || `DNSimple HTTP ${response.status}`);
657
+ return { status: response.status, body };
658
+ }
659
+ return {
660
+ name: "dnsimple",
661
+ async apply(domain, records) {
662
+ const { zone } = await resolveZone(domain, "DNSimple", async (candidate) => {
663
+ const { status } = await dnsimple(`/zones/${encodeURIComponent(candidate)}`);
664
+ return status === 404 ? null : true;
665
+ });
666
+ const path = `/zones/${encodeURIComponent(zone)}/records`;
667
+ const body = (record) => ({
668
+ name: record.name,
669
+ type: record.type,
670
+ content: record.value,
671
+ ttl: 3600,
672
+ ...record.priority != null ? { priority: record.priority } : {}
673
+ });
674
+ const write = async (method, suffix, record) => {
675
+ const { status } = await dnsimple(`${path}${suffix}`, { method, body: JSON.stringify(body(record)) });
676
+ if (status === 404) throw new Error(`DNSimple lost the ${zone} zone mid-update`);
677
+ };
678
+ return applyToRecordStore(zone, records, {
679
+ async list() {
680
+ const rows = [];
681
+ for (let page = 1; page <= 20; page++) {
682
+ const { body: list } = await dnsimple(`${path}?per_page=100&page=${page}`);
683
+ rows.push(...list.data || []);
684
+ if ((list.pagination?.total_pages ?? 1) <= page) break;
685
+ }
686
+ return rows.filter((row) => !row.system_record).map((row) => ({
687
+ id: String(row.id),
688
+ name: row.name.toLowerCase(),
689
+ type: row.type,
690
+ value: row.content,
691
+ priority: row.priority ?? void 0
692
+ }));
693
+ },
694
+ create: (record) => write("POST", "", record),
695
+ update: (existing, record) => write("PATCH", `/${existing.id}`, record)
696
+ });
697
+ }
698
+ };
699
+ }
700
+
701
+ // src/dns/netlify.ts
702
+ function createNetlifyProvider(options) {
703
+ const fetchImpl = options.fetchImpl || fetch;
704
+ async function netlify(path, init) {
705
+ const response = await fetchImpl(`https://api.netlify.com/api/v1${path}`, {
706
+ ...init,
707
+ headers: { Authorization: `Bearer ${options.apiToken}`, "Content-Type": "application/json", ...init?.headers || {} }
708
+ });
709
+ const body = await response.json().catch(() => ({}));
710
+ if (!response.ok) throw new Error(body.message || `Netlify HTTP ${response.status}`);
711
+ return body;
712
+ }
713
+ return {
714
+ name: "netlify",
715
+ async apply(domain, records) {
716
+ const zones = await netlify("/dns_zones");
717
+ const { zone, info } = await resolveZone(
718
+ domain,
719
+ "Netlify",
720
+ async (candidate) => zones.find((z) => z.name.toLowerCase() === candidate) ?? null
721
+ );
722
+ const body = (record) => ({
723
+ type: record.type,
724
+ hostname: fqdn(record.name, zone),
725
+ value: record.value,
726
+ ttl: 3600,
727
+ ...record.priority != null ? { priority: record.priority } : {}
728
+ });
729
+ const create = async (record) => {
730
+ await netlify(`/dns_zones/${info.id}/dns_records`, { method: "POST", body: JSON.stringify(body(record)) });
731
+ };
732
+ return applyToRecordStore(zone, records, {
733
+ async list() {
734
+ const rows = await netlify(`/dns_zones/${info.id}/dns_records`);
735
+ return rows.filter((row) => !row.managed).map((row) => ({
736
+ id: row.id,
737
+ name: relativeName3(row.hostname, zone),
738
+ type: row.type,
739
+ value: row.value,
740
+ priority: row.priority ?? void 0
741
+ }));
742
+ },
743
+ create,
744
+ // TXT: add the new value before removing the old, so SPF never disappears. A
745
+ // name can only hold one CNAME, so that one goes the other way round.
746
+ async update(existing, record) {
747
+ const remove = () => netlify(`/dns_zones/${info.id}/dns_records/${existing.id}`, { method: "DELETE" });
748
+ if (record.type === "CNAME") {
749
+ await remove();
750
+ await create(record);
751
+ } else {
752
+ await create(record);
753
+ await remove();
754
+ }
755
+ }
756
+ });
757
+ }
758
+ };
759
+ }
760
+
761
+ // src/dns/vercel.ts
762
+ function zoneNameCandidates3(domain) {
763
+ const labels = domain.replace(/\.$/, "").toLowerCase().split(".").filter(Boolean);
764
+ const candidates = [];
765
+ for (let i = 0; i <= labels.length - 2; i++) {
766
+ candidates.push(labels.slice(i).join("."));
767
+ }
768
+ return candidates;
769
+ }
770
+ function relativeName4(fqdn2, zone) {
771
+ const domain = zone.replace(/\.$/, "").toLowerCase();
772
+ const host = fqdn2.replace(/\.$/, "").toLowerCase();
486
773
  if (host === domain) return "";
487
774
  if (host.endsWith(`.${domain}`)) return host.slice(0, -(domain.length + 1));
488
775
  return host;
@@ -530,7 +817,7 @@ function createVercelProvider(options) {
530
817
  }
531
818
  async function resolveDomain(domain) {
532
819
  const errors = [];
533
- for (const candidate of zoneNameCandidates2(domain)) {
820
+ for (const candidate of zoneNameCandidates3(domain)) {
534
821
  try {
535
822
  await listRecords(candidate);
536
823
  return candidate;
@@ -554,7 +841,7 @@ function createVercelProvider(options) {
554
841
  };
555
842
  for (const record of records) {
556
843
  const recordType = record.type.toUpperCase();
557
- const name = relativeName3(record.host, zone);
844
+ const name = relativeName4(record.host, zone);
558
845
  if (recordType === "CNAME") {
559
846
  const target = record.value.replace(/\.$/, "").toLowerCase();
560
847
  const existing = cached.find(
@@ -726,6 +1013,24 @@ function createDnsProvider(provider, credentials, fetchImpl) {
726
1013
  fetchImpl
727
1014
  });
728
1015
  }
1016
+ if (provider === "netlify") {
1017
+ const apiToken = credentials.netlifyApiToken;
1018
+ if (!apiToken) throw new Error("Missing Netlify token. Set NETLIFY_AUTH_TOKEN or pass netlifyApiToken.");
1019
+ return createNetlifyProvider({ apiToken, fetchImpl });
1020
+ }
1021
+ if (provider === "digitalocean") {
1022
+ const apiToken = credentials.digitaloceanApiToken;
1023
+ if (!apiToken) throw new Error("Missing DigitalOcean token. Set DIGITALOCEAN_TOKEN or pass digitaloceanApiToken.");
1024
+ return createDigitalOceanProvider({ apiToken, fetchImpl });
1025
+ }
1026
+ if (provider === "dnsimple") {
1027
+ const apiToken = credentials.dnsimpleApiToken;
1028
+ const accountId = credentials.dnsimpleAccountId;
1029
+ if (!apiToken || !accountId) {
1030
+ throw new Error("Missing DNSimple credentials. Set DNSIMPLE_TOKEN and DNSIMPLE_ACCOUNT_ID.");
1031
+ }
1032
+ return createDnsimpleProvider({ apiToken, accountId, fetchImpl });
1033
+ }
729
1034
  throw new Error(`Unsupported DNS provider: ${provider}`);
730
1035
  }
731
1036
  async function applyDnsRecords(input) {
@@ -1059,8 +1364,9 @@ var DomainsResource = class {
1059
1364
  });
1060
1365
  }
1061
1366
  /**
1062
- * Apply DNS using the public API. Cloudflare, Vercel, and GoDaddy can use stored dashboard
1063
- * credentials; GoDaddy also accepts one-time credentials on the request.
1367
+ * Apply DNS using the public API with the DNS host connected in the dashboard (Cloudflare,
1368
+ * Vercel, GoDaddy, Netlify, DigitalOcean or DNSimple). GoDaddy also accepts one-time
1369
+ * credentials on the request.
1064
1370
  */
1065
1371
  async apply(idOrName, options = {}) {
1066
1372
  return this.client.request(`/domains/${encodeURIComponent(idOrName)}`, {
@@ -1073,8 +1379,8 @@ var DomainsResource = class {
1073
1379
  });
1074
1380
  }
1075
1381
  /**
1076
- * Fetch Groundmail DNS records, then write them at Cloudflare, Vercel, or GoDaddy
1077
- * using the caller's provider credentials (never sent to Groundmail).
1382
+ * Fetch Groundmail DNS records, then write them at Cloudflare, Vercel, GoDaddy, Netlify,
1383
+ * DigitalOcean or DNSimple using the caller's provider credentials (never sent to Groundmail).
1078
1384
  */
1079
1385
  async applyDns(params) {
1080
1386
  const detail = await this.get(params.idOrName);
@@ -1092,7 +1398,11 @@ var DomainsResource = class {
1092
1398
  godaddyApiKey: params.godaddyApiKey,
1093
1399
  godaddyApiSecret: params.godaddyApiSecret,
1094
1400
  vercelApiToken: params.vercelApiToken,
1095
- vercelTeamId: params.vercelTeamId
1401
+ vercelTeamId: params.vercelTeamId,
1402
+ netlifyApiToken: params.netlifyApiToken,
1403
+ digitaloceanApiToken: params.digitaloceanApiToken,
1404
+ dnsimpleApiToken: params.dnsimpleApiToken,
1405
+ dnsimpleAccountId: params.dnsimpleAccountId
1096
1406
  },
1097
1407
  fetchImpl: this.client.fetchImpl
1098
1408
  });
package/dist/clerk.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { G as Groundmail, S as SendEmailResult } from './client-DZNmwhee.js';
1
+ import { G as Groundmail, S as SendEmailResult } from './client-Btlt7wMg.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-E6G7G2PM.js";
4
+ } from "./chunk-RLIF6XKO.js";
5
5
 
6
6
  // src/clerk.ts
7
7
  function resolveApiKey(options) {
@@ -1013,7 +1013,7 @@ type UpdateDomainParams = {
1013
1013
  /** What to do with received mail at or above the threshold. */
1014
1014
  inbound_spam_action?: 'flag' | 'drop';
1015
1015
  };
1016
- type DnsProviderName = 'cloudflare' | 'godaddy' | 'vercel';
1016
+ type DnsProviderName = 'cloudflare' | 'godaddy' | 'vercel' | 'netlify' | 'digitalocean' | 'dnsimple';
1017
1017
  type RemoteApplyDnsCredentials = {
1018
1018
  /** Inline Cloudflare token instead of the stored dashboard integration. */
1019
1019
  apiToken?: string;
@@ -1027,6 +1027,13 @@ type RemoteApplyDnsCredentials = {
1027
1027
  godaddyApiSecret?: string;
1028
1028
  vercelApiToken?: string;
1029
1029
  vercelTeamId?: string;
1030
+ /** Inline token for netlify, digitalocean or dnsimple instead of the stored connection. */
1031
+ netlifyApiToken?: string;
1032
+ digitaloceanApiToken?: string;
1033
+ dnsimpleApiToken?: string;
1034
+ /** DNSimple account the inline token belongs to. */
1035
+ dnsimpleAccountId?: string;
1036
+ accountId?: string;
1030
1037
  };
1031
1038
  type ApplyDnsParams = {
1032
1039
  provider: DnsProviderName;
@@ -1038,6 +1045,10 @@ type ApplyDnsParams = {
1038
1045
  godaddyApiSecret?: string;
1039
1046
  vercelApiToken?: string;
1040
1047
  vercelTeamId?: string;
1048
+ netlifyApiToken?: string;
1049
+ digitaloceanApiToken?: string;
1050
+ dnsimpleApiToken?: string;
1051
+ dnsimpleAccountId?: string;
1041
1052
  };
1042
1053
  type ApplyDnsRecordResult = {
1043
1054
  purpose: string;
@@ -1514,7 +1525,7 @@ type DmarcMonitoringDisabled = {
1514
1525
  type DmarcStatus = DmarcMonitoring | DmarcMonitoringDisabled;
1515
1526
  type EnableDmarcParams = {
1516
1527
  /** Also write the record through a DNS provider connected under Settings → Integrations. */
1517
- apply?: 'cloudflare' | 'vercel' | 'godaddy';
1528
+ apply?: DnsProviderName;
1518
1529
  digest_enabled?: boolean;
1519
1530
  };
1520
1531
  type EnableDmarcResult = {
@@ -1561,16 +1572,17 @@ declare class DomainsResource {
1561
1572
  ok: boolean;
1562
1573
  }>;
1563
1574
  /**
1564
- * Apply DNS using the public API. Cloudflare, Vercel, and GoDaddy can use stored dashboard
1565
- * credentials; GoDaddy also accepts one-time credentials on the request.
1575
+ * Apply DNS using the public API with the DNS host connected in the dashboard (Cloudflare,
1576
+ * Vercel, GoDaddy, Netlify, DigitalOcean or DNSimple). GoDaddy also accepts one-time
1577
+ * credentials on the request.
1566
1578
  */
1567
1579
  apply(idOrName: string, options?: {
1568
- provider?: 'cloudflare' | 'godaddy' | 'vercel';
1580
+ provider?: DnsProviderName;
1569
1581
  credentials?: RemoteApplyDnsCredentials;
1570
1582
  }): Promise<ApplyDnsResult>;
1571
1583
  /**
1572
- * Fetch Groundmail DNS records, then write them at Cloudflare, Vercel, or GoDaddy
1573
- * using the caller's provider credentials (never sent to Groundmail).
1584
+ * Fetch Groundmail DNS records, then write them at Cloudflare, Vercel, GoDaddy, Netlify,
1585
+ * DigitalOcean or DNSimple using the caller's provider credentials (never sent to Groundmail).
1574
1586
  */
1575
1587
  applyDns(params: ApplyDnsParams): Promise<ApplyDnsResult>;
1576
1588
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as ApiErrorBody, D as DnsRecord } from './client-DZNmwhee.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 CategoryOptOut, s as CategoryOptOutCategory, t as CategoryUnsubscribedErrorDetails, u as ConnectTwilioParams, v as ContactWebhookEventPayload, w as CreateApiKeyParams, x as CreateApiKeyResult, y as CreateAutomationParams, z as CreateDomainParams, E as CreateDomainResult, F as CreateMigrationParams, H as CreateSuppressionParams, I as CreateTemplateParams, J as CreateTenantParams, K as CreateWebhookParams, L as DEFAULT_API_BASE_URL, M as DailyRampLimitErrorDetails, N as DeleteTenantResult, O as DeliverabilityBucket, P as DeliverabilityDomainInsight, Q as DeliverabilityInsightsResult, R as DeliverabilityMailServerRelay, T as DeliverabilityWindow, U as DmarcDailyPoint, V as DmarcMonitoring, W as DmarcMonitoringDisabled, X as DmarcReadiness, Y as DmarcReadinessVerdict, Z as DmarcSource, _ as DmarcSourceStatus, $ as DmarcStatus, a0 as DnsProviderName, a1 as Domain, a2 as DomainBimiAnalysis, a3 as DomainDetailResult, a4 as DomainDmarcAnalysis, a5 as DomainDnsAnalysis, a6 as DomainStatus, a7 as DomainTlsMode, a8 as DomainTrackingDns, a9 as DomainTrackingStatus, aa as EmailListItem, ab as EmailWebhookEventPayload, ac as EmailWebhookEventType, ad as EnableDmarcParams, ae as EnableDmarcResult, af as EnrollmentSkip, ag as EventRecord, G as Groundmail, ah as GroundmailAccessToken, ai as GroundmailOptions, aj as GroundmailTokenOptions, ak as GroundmailTokenProvider, al as HeldBackErrorDetails, am as HeldBackReason, an as HeldBackRecipient, ao as ListApiKeysParams, ap as ListApiKeysResult, aq as ListAutomationRunsParams, ar as ListAutomationRunsResult, as as ListAutomationsResult, at as ListCategoryOptOutsParams, au as ListCategoryOptOutsResult, av as ListDomainsParams, aw as ListDomainsResult, ax as ListEmailsParams, ay as ListEmailsResult, az as ListReceivedEmailsParams, aA as ListReceivedEmailsResult, aB as ListSuppressionsParams, aC as ListSuppressionsResult, aD as ListTemplatesParams, aE as ListTemplatesResult, aF as ListTenantDomainsResult, aG as ListTenantsParams, aH as ListTenantsResult, aI as ListTestInboxEmailsParams, aJ as ListTestInboxEmailsResult, aK as MailServerSigningStatus, aL as MailStream, aM as Migration, aN as MigrationDnsChange, aO as MigrationDnsPlan, aP as MigrationDomainItem, aQ as MigrationItemStatus, aR as MigrationProvider, aS as MigrationResource, aT as MigrationSummary, aU as MigrationTemplateItem, aV as MigrationTotals, aW as MigrationWebhookItem, aX as ReceivedEmail, aY as ReceivedEmailAttachment, aZ as ReceivedEmailAttachmentMeta, a_ as RemoteApplyDnsCredentials, a$ as RemoveCategoryOptOutOptions, b0 as RunMigrationOptions, b1 as SendEmailParams, S as SendEmailResult, b2 as StreamDeliverability, b3 as SuppressedErrorDetails, b4 as Suppression, b5 as TEST_INBOX_WAIT_DEFAULT_MS, b6 as TEST_INBOX_WAIT_MAX_SECONDS, b7 as Template, b8 as TemplateFormat, b9 as TemplateLintWarning, ba as TemplateStarter, bb as TemplateStatus, bc as TemplateVersion, bd as Tenant, be as TenantPauseSource, bf as TenantRamp, bg as TenantStats, bh as TenantStatsResult, bi as TenantStatus, bj as TenantStreamPolicy, bk as TestEmailWebhookParams, bl as TestEmailWebhookResult, bm as TestInbox, bn as TestInboxAddressOptions, bo as TestInboxEmail, bp as TestInboxEmailDetail, bq as TestInboxHeader, br as TestInboxResource, bs as TestInboxUsage, bt as TriggerEventParams, bu as TriggerEventResult, bv as TwilioIntegrationResponse, bw as TwilioIntegrationStatus, bx as UpdateAutomationParams, by as UpdateDomainParams, bz as UpdateEmailParams, bA as UpdateTemplateParams, bB as UpdateTenantParams, bC as UpdateWebhookParams, bD as ValidateEmailResult, bE as ValidationReason, bF as ValidationVerdict, bG as VerifyDomainResult, bH as WaitForTestEmailParams, bI as WebhookEmailTag, bJ as WebhookEndpoint, bK as WebhookEndpointWithSecret, bL as WebhookEventType, bM as mergeMigrations, bN as normalizeAccessToken } from './client-DZNmwhee.js';
1
+ import { A as ApiErrorBody, D as DnsRecord } from './client-Btlt7wMg.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 CategoryOptOut, s as CategoryOptOutCategory, t as CategoryUnsubscribedErrorDetails, u as ConnectTwilioParams, v as ContactWebhookEventPayload, w as CreateApiKeyParams, x as CreateApiKeyResult, y as CreateAutomationParams, z as CreateDomainParams, E as CreateDomainResult, F as CreateMigrationParams, H as CreateSuppressionParams, I as CreateTemplateParams, J as CreateTenantParams, K as CreateWebhookParams, L as DEFAULT_API_BASE_URL, M as DailyRampLimitErrorDetails, N as DeleteTenantResult, O as DeliverabilityBucket, P as DeliverabilityDomainInsight, Q as DeliverabilityInsightsResult, R as DeliverabilityMailServerRelay, T as DeliverabilityWindow, U as DmarcDailyPoint, V as DmarcMonitoring, W as DmarcMonitoringDisabled, X as DmarcReadiness, Y as DmarcReadinessVerdict, Z as DmarcSource, _ as DmarcSourceStatus, $ as DmarcStatus, a0 as DnsProviderName, a1 as Domain, a2 as DomainBimiAnalysis, a3 as DomainDetailResult, a4 as DomainDmarcAnalysis, a5 as DomainDnsAnalysis, a6 as DomainStatus, a7 as DomainTlsMode, a8 as DomainTrackingDns, a9 as DomainTrackingStatus, aa as EmailListItem, ab as EmailWebhookEventPayload, ac as EmailWebhookEventType, ad as EnableDmarcParams, ae as EnableDmarcResult, af as EnrollmentSkip, ag as EventRecord, G as Groundmail, ah as GroundmailAccessToken, ai as GroundmailOptions, aj as GroundmailTokenOptions, ak as GroundmailTokenProvider, al as HeldBackErrorDetails, am as HeldBackReason, an as HeldBackRecipient, ao as ListApiKeysParams, ap as ListApiKeysResult, aq as ListAutomationRunsParams, ar as ListAutomationRunsResult, as as ListAutomationsResult, at as ListCategoryOptOutsParams, au as ListCategoryOptOutsResult, av as ListDomainsParams, aw as ListDomainsResult, ax as ListEmailsParams, ay as ListEmailsResult, az as ListReceivedEmailsParams, aA as ListReceivedEmailsResult, aB as ListSuppressionsParams, aC as ListSuppressionsResult, aD as ListTemplatesParams, aE as ListTemplatesResult, aF as ListTenantDomainsResult, aG as ListTenantsParams, aH as ListTenantsResult, aI as ListTestInboxEmailsParams, aJ as ListTestInboxEmailsResult, aK as MailServerSigningStatus, aL as MailStream, aM as Migration, aN as MigrationDnsChange, aO as MigrationDnsPlan, aP as MigrationDomainItem, aQ as MigrationItemStatus, aR as MigrationProvider, aS as MigrationResource, aT as MigrationSummary, aU as MigrationTemplateItem, aV as MigrationTotals, aW as MigrationWebhookItem, aX as ReceivedEmail, aY as ReceivedEmailAttachment, aZ as ReceivedEmailAttachmentMeta, a_ as RemoteApplyDnsCredentials, a$ as RemoveCategoryOptOutOptions, b0 as RunMigrationOptions, b1 as SendEmailParams, S as SendEmailResult, b2 as StreamDeliverability, b3 as SuppressedErrorDetails, b4 as Suppression, b5 as TEST_INBOX_WAIT_DEFAULT_MS, b6 as TEST_INBOX_WAIT_MAX_SECONDS, b7 as Template, b8 as TemplateFormat, b9 as TemplateLintWarning, ba as TemplateStarter, bb as TemplateStatus, bc as TemplateVersion, bd as Tenant, be as TenantPauseSource, bf as TenantRamp, bg as TenantStats, bh as TenantStatsResult, bi as TenantStatus, bj as TenantStreamPolicy, bk as TestEmailWebhookParams, bl as TestEmailWebhookResult, bm as TestInbox, bn as TestInboxAddressOptions, bo as TestInboxEmail, bp as TestInboxEmailDetail, bq as TestInboxHeader, br as TestInboxResource, bs as TestInboxUsage, bt as TriggerEventParams, bu as TriggerEventResult, bv as TwilioIntegrationResponse, bw as TwilioIntegrationStatus, bx as UpdateAutomationParams, by as UpdateDomainParams, bz as UpdateEmailParams, bA as UpdateTemplateParams, bB as UpdateTenantParams, bC as UpdateWebhookParams, bD as ValidateEmailResult, bE as ValidationReason, bF as ValidationVerdict, bG as VerifyDomainResult, bH as WaitForTestEmailParams, bI as WebhookEmailTag, bJ as WebhookEndpoint, bK as WebhookEndpointWithSecret, bL as WebhookEventType, bM as mergeMigrations, bN as normalizeAccessToken } from './client-Btlt7wMg.js';
3
3
 
4
4
  type GroundmailErrorOptions = {
5
5
  code?: string;
@@ -80,7 +80,7 @@ declare function isTestApiKey(key: string | null | undefined): boolean;
80
80
  /** Merge `include:host` into an existing SPF record (or create a minimal one). */
81
81
  declare function mergeSpfInclude(existing: string | null | undefined, includeHost: string): string;
82
82
 
83
- type DnsProviderName = 'cloudflare' | 'godaddy' | 'vercel';
83
+ type DnsProviderName = 'cloudflare' | 'godaddy' | 'vercel' | 'netlify' | 'digitalocean' | 'dnsimple';
84
84
  type ApplyDnsRecordResult = {
85
85
  purpose: string;
86
86
  host: string;
@@ -99,6 +99,10 @@ type ApplyDnsCredentials = {
99
99
  godaddyApiSecret?: string;
100
100
  vercelApiToken?: string;
101
101
  vercelTeamId?: string;
102
+ netlifyApiToken?: string;
103
+ digitaloceanApiToken?: string;
104
+ dnsimpleApiToken?: string;
105
+ dnsimpleAccountId?: string;
102
106
  };
103
107
 
104
108
  declare function applyDnsRecords(input: {
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  normalizeAccessToken,
13
13
  signWebhookPayload,
14
14
  verifyWebhookSignature
15
- } from "./chunk-E6G7G2PM.js";
15
+ } from "./chunk-RLIF6XKO.js";
16
16
 
17
17
  // src/simulator.ts
18
18
  var SIMULATOR_DOMAIN = "simulator.groundmail.dev";
package/dist/payload.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { G as Groundmail } from './client-DZNmwhee.js';
1
+ import { G as Groundmail } from './client-Btlt7wMg.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-E6G7G2PM.js";
4
+ } from "./chunk-RLIF6XKO.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.26.1",
3
+ "version": "0.27.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.14"
92
+ "groundmail-nodemailer": "0.15.15"
93
93
  },
94
94
  "devDependencies": {
95
95
  "@types/nodemailer": "^8.0.1",