@samva/cli 0.8.0 → 0.9.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.
Files changed (3) hide show
  1. package/README.md +10 -3
  2. package/dist/bin.mjs +236 -39
  3. package/package.json +7 -7
package/README.md CHANGED
@@ -420,13 +420,20 @@ review decision, sendable domain and verified sender counts, and domain-verifica
420
420
  `readyToSend` is true when a production send would be admitted and at least one domain or sender can
421
421
  carry it. It is read-only and does not inspect receiving configuration, check DNS itself, send an
422
422
  email, or replay fixtures.
423
- Use `samva email receiving status <domain-id>` to read a domain's receiving prerequisite status.
424
- `samva email receiving enable <domain-id> ...` acts by default; its `--dry-run` is offline and does
425
- not require credentials or call the API. Each check is `passed`, `failed`, or
423
+ Each check is `passed`, `failed`, or
426
424
  `unavailable`. A `failed` check carries the underlying error detail; `unavailable`, such as no
427
425
  sending domain yet or a review in progress, is informational; a rejected review fails. The command always prints the full report, then
428
426
  exits `1` if any check failed and `0` otherwise. The `--json` result's `data` adds an aggregate
429
427
  `ok` boolean, and human output marks checks with `✓`, `!`, and `✗` respectively.
430
428
 
429
+ Use `samva email receiving status <domain-id>` to read a domain's receiving prerequisites in
430
+ order: sending verified, a destination, receiving enabled, and the inbound MX, which it looks up
431
+ live and names when mail is routed elsewhere.
432
+ `samva email receiving enable <domain-id> --domain <name> --catch-all [--endpoint <webhook-id>]`
433
+ routes every address on a verified domain. A catch-all needs a destination, either `--endpoint` or
434
+ an active forwarding rule that covers the domain; mail that matches neither is rejected. Pass
435
+ `--address` for exact recipients instead. The command acts by default; its `--dry-run` is offline
436
+ and does not require credentials or call the API.
437
+
431
438
  A command that needs a subcommand, such as a bare `samva` or a group like `samva email`, prints its
432
439
  help to stdout and exits `2`, so a script never mistakes a missing subcommand for success.
package/dist/bin.mjs CHANGED
@@ -13982,7 +13982,7 @@ var layer12 = /* @__PURE__ */ layer.pipe(/* @__PURE__ */ provideMerge(/* @__PURE
13982
13982
  // package.json
13983
13983
  var package_default = {
13984
13984
  name: "@samva/cli",
13985
- version: "0.8.0",
13985
+ version: "0.9.0",
13986
13986
  description: "Samva CLI \u2014 email and platform operations from your terminal",
13987
13987
  keywords: [
13988
13988
  "cli",
@@ -14037,16 +14037,16 @@ var package_default = {
14037
14037
  "@types/node": "catalog:",
14038
14038
  "better-auth": "catalog:",
14039
14039
  effect: "catalog:effect",
14040
- samva: "workspace:0.11.0",
14040
+ samva: "workspace:0.12.0",
14041
14041
  typescript: "catalog:",
14042
14042
  vite: "~8.3.0",
14043
14043
  vitest: "catalog:"
14044
14044
  },
14045
14045
  optionalDependencies: {
14046
- "@samva/cli-darwin-arm64": "workspace:0.8.0",
14047
- "@samva/cli-darwin-x64": "workspace:0.8.0",
14048
- "@samva/cli-linux-arm64": "workspace:0.8.0",
14049
- "@samva/cli-linux-x64": "workspace:0.8.0"
14046
+ "@samva/cli-darwin-arm64": "workspace:0.9.0",
14047
+ "@samva/cli-darwin-x64": "workspace:0.9.0",
14048
+ "@samva/cli-linux-arm64": "workspace:0.9.0",
14049
+ "@samva/cli-linux-x64": "workspace:0.9.0"
14050
14050
  },
14051
14051
  engines: {
14052
14052
  node: ">=18"
@@ -43690,8 +43690,8 @@ function buildMessagesSendPayload(input) {
43690
43690
  };
43691
43691
  }
43692
43692
 
43693
- // ../sdk/dist/client-attribution-Ct2FKITg.js
43694
- var SDK_VERSION = "0.11.0";
43693
+ // ../sdk/dist/client-attribution-BTWYSXxo.js
43694
+ var SDK_VERSION = "0.12.0";
43695
43695
  var CLIENT_ATTRIBUTION_HEADER = "x-samva-client";
43696
43696
  var clientAttribution = (surface) => `${surface}/${SDK_VERSION}`;
43697
43697
 
@@ -46053,7 +46053,7 @@ var fetch2 = /* @__PURE__ */ make34((request, url, signal, fiber) => {
46053
46053
  });
46054
46054
  var layer15 = /* @__PURE__ */ layerMergedContext(/* @__PURE__ */ succeed6(fetch2));
46055
46055
 
46056
- // ../sdk/dist/client-8YXjfsVc.js
46056
+ // ../sdk/dist/client-Cj1bqIEe.js
46057
46057
  var Sensitive = (schema) => RedactedFromValue(schema);
46058
46058
  var SensitiveString = Sensitive(String4);
46059
46059
  var ABSOLUTE_ISO_INSTANT_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,9}))?)?(Z|([+-])(\d{2}):(\d{2}))$/;
@@ -46381,6 +46381,25 @@ var EmailChannelError = class extends TaggedError3()("EmailChannelError", Struct
46381
46381
  statusCode: optionalKey2(Union2([Finite.check(isFinite4().annotate({ expected: "a finite number" })), Null2]))
46382
46382
  })) {
46383
46383
  };
46384
+ var CustomTrackingCertificate = Struct({
46385
+ reason: Struct({
46386
+ code: Union2([
46387
+ Literal2("initializing"),
46388
+ Literal2("awaiting-hostname"),
46389
+ Literal2("awaiting-dcv"),
46390
+ Literal2("issuing"),
46391
+ Literal2("caa-blocked"),
46392
+ Literal2("validation-error"),
46393
+ Literal2("timed-out"),
46394
+ Literal2("verifying-route"),
46395
+ Literal2("ready"),
46396
+ Literal2("provider-unreachable")
46397
+ ]),
46398
+ message: String4
46399
+ }),
46400
+ lastCheckedAt: Union2([String4, Null2]),
46401
+ nextCheckAt: Union2([String4, Null2])
46402
+ }).annotate({ identifier: "CustomTrackingCertificate" });
46384
46403
  var CustomTrackingDnsRecord = Struct({
46385
46404
  name: String4,
46386
46405
  type: String4,
@@ -47727,6 +47746,49 @@ var BlockLogPage = Struct({
47727
47746
  })),
47728
47747
  nextCursor: Union2([String4, Null2])
47729
47748
  }).annotate({ identifier: "BlockLogPage" });
47749
+ var ReceivingStatus = Struct({
47750
+ domain: String4,
47751
+ receivingHost: String4,
47752
+ sending: Struct({
47753
+ satisfied: Boolean3,
47754
+ phase: Union2([
47755
+ Literal2("dns-setup"),
47756
+ Literal2("provisioning"),
47757
+ Literal2("verifying"),
47758
+ Literal2("ready"),
47759
+ Literal2("degraded"),
47760
+ Literal2("failed")
47761
+ ])
47762
+ }),
47763
+ destination: Struct({
47764
+ satisfied: Boolean3,
47765
+ endpointId: Union2([WebhookEndpointId, Null2]),
47766
+ forwardingRules: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })),
47767
+ mailboxes: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" }))
47768
+ }),
47769
+ receiving: Struct({
47770
+ satisfied: Boolean3,
47771
+ catchAll: Boolean3
47772
+ }),
47773
+ mx: Struct({
47774
+ satisfied: Boolean3,
47775
+ state: Union2([
47776
+ Literal2("pointing-here"),
47777
+ Literal2("elsewhere"),
47778
+ Literal2("missing"),
47779
+ Literal2("unknown")
47780
+ ]),
47781
+ expected: Struct({
47782
+ exchange: String4,
47783
+ priority: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" }))
47784
+ }),
47785
+ current: ArraySchema(Struct({
47786
+ exchange: String4,
47787
+ priority: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" }))
47788
+ }))
47789
+ }),
47790
+ ready: Boolean3
47791
+ }).annotate({ identifier: "ReceivingStatus" });
47730
47792
  var WebhookEndpoint = Struct({
47731
47793
  id: WebhookEndpointId,
47732
47794
  name: String4,
@@ -47879,6 +47941,7 @@ var CustomTrackingStatus = Struct({
47879
47941
  hostname: String4,
47880
47942
  region: String4,
47881
47943
  sslStatus: Union2([String4, Null2]),
47944
+ certificate: Union2([CustomTrackingCertificate, Null2]),
47882
47945
  dnsRecords: Struct({
47883
47946
  tracking: Union2([CustomTrackingDnsRecord, Null2]),
47884
47947
  dcvDelegation: Union2([CustomTrackingDnsRecord, Null2])
@@ -47967,10 +48030,11 @@ var ForwardingRuleList = Struct({ items: ArraySchema(Struct({
47967
48030
  id: EmailForwardingRuleId,
47968
48031
  name: String4,
47969
48032
  description: Union2([String4, Null2]),
48033
+ domainId: Union2([EmailDomainId, Null2]),
47970
48034
  sourcePattern: Union2([String4, Null2]),
47971
48035
  forwardTo: ArraySchema(String4),
47972
48036
  preserveHeaders: Boolean3,
47973
- priority: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })),
48037
+ priority: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })).check(isLessThanOrEqualTo3(2147483647).annotate({ expected: "a value less than or equal to 2147483647" })),
47974
48038
  isActive: Boolean3,
47975
48039
  forwardCount: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })),
47976
48040
  failureCount: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })),
@@ -51367,6 +51431,16 @@ var EmailCompleteCustomTrackingDomainConnect403 = ForbiddenError;
51367
51431
  var EmailCompleteCustomTrackingDomainConnect422 = ValidationError;
51368
51432
  var EmailCompleteCustomTrackingDomainConnect500 = InternalError;
51369
51433
  var EmailCompleteCustomTrackingDomainConnect504 = GatewayTimeoutError;
51434
+ var EmailRecheckCustomTrackingPathParams = Struct({ id: EmailDomainId });
51435
+ var EmailRecheckCustomTracking200 = CustomTrackingStatus;
51436
+ var EmailRecheckCustomTracking401 = UnauthorizedError;
51437
+ var EmailRecheckCustomTracking403 = ForbiddenError;
51438
+ var EmailRecheckCustomTracking404 = ResourceNotFoundError;
51439
+ var EmailRecheckCustomTracking409 = ConflictError;
51440
+ var EmailRecheckCustomTracking422 = ValidationError;
51441
+ var EmailRecheckCustomTracking500 = InternalError;
51442
+ var EmailRecheckCustomTracking502 = EmailChannelError;
51443
+ var EmailRecheckCustomTracking504 = GatewayTimeoutError;
51370
51444
  var EmailSetDefaultDomainPathParams = Struct({ id: EmailDomainId });
51371
51445
  var EmailSetDefaultDomain200 = SuccessResult;
51372
51446
  var EmailSetDefaultDomain401 = UnauthorizedError;
@@ -51436,7 +51510,7 @@ var EmailCompleteDomainConnect500 = InternalError;
51436
51510
  var EmailCompleteDomainConnect502 = EmailChannelError;
51437
51511
  var EmailCompleteDomainConnect504 = GatewayTimeoutError;
51438
51512
  var EmailEnableCatchAllPathParams = Struct({ id: EmailDomainId });
51439
- var EmailEnableCatchAllRequestJson = Struct({ endpointId: WebhookEndpointId });
51513
+ var EmailEnableCatchAllRequestJson = Struct({ endpointId: optionalKey2(Union2([WebhookEndpointId, Null2])) });
51440
51514
  var EmailEnableCatchAll201 = Struct({ success: Literal2(true) });
51441
51515
  var EmailEnableCatchAll401 = UnauthorizedError;
51442
51516
  var EmailEnableCatchAll403 = ForbiddenError;
@@ -51462,6 +51536,14 @@ var EmailEnableReceiving404 = ResourceNotFoundError;
51462
51536
  var EmailEnableReceiving422 = ValidationError;
51463
51537
  var EmailEnableReceiving500 = InternalError;
51464
51538
  var EmailEnableReceiving504 = GatewayTimeoutError;
51539
+ var EmailGetReceivingStatusPathParams = Struct({ id: EmailDomainId });
51540
+ var EmailGetReceivingStatus200 = ReceivingStatus;
51541
+ var EmailGetReceivingStatus401 = UnauthorizedError;
51542
+ var EmailGetReceivingStatus403 = ForbiddenError;
51543
+ var EmailGetReceivingStatus404 = ResourceNotFoundError;
51544
+ var EmailGetReceivingStatus422 = ValidationError;
51545
+ var EmailGetReceivingStatus500 = InternalError;
51546
+ var EmailGetReceivingStatus504 = GatewayTimeoutError;
51465
51547
  var EmailDetectRegistrarPathParams = Struct({ id: EmailDomainId });
51466
51548
  var EmailDetectRegistrarRequestJson = Struct({ domain: String4 });
51467
51549
  var EmailDetectRegistrar200 = Struct({
@@ -51607,7 +51689,7 @@ var EmailCreateForwardingRuleRequestJson = Struct({
51607
51689
  preserveHeaders: optionalKey2(Union2([Boolean3, Null2])),
51608
51690
  appendNote: optionalKey2(Union2([String4, Null2])),
51609
51691
  conditions: optionalKey2(Union2([ForwardingConditions, Null2])),
51610
- priority: optionalKey2(Union2([Finite.check(isFinite4().annotate({ expected: "a finite number" })), Null2])),
51692
+ priority: optionalKey2(Union2([Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })).check(isLessThanOrEqualTo3(2147483647).annotate({ expected: "a value less than or equal to 2147483647" })), Null2])),
51611
51693
  isActive: optionalKey2(Union2([Boolean3, Null2])),
51612
51694
  metadata: optionalKey2(Union2([Union2([Record(String4, JsonValue), Null2]), Null2]))
51613
51695
  });
@@ -51615,7 +51697,7 @@ var EmailCreateForwardingRule201 = Struct({
51615
51697
  id: EmailForwardingRuleId,
51616
51698
  name: String4,
51617
51699
  forwardTo: ArraySchema(String4),
51618
- priority: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })),
51700
+ priority: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })).check(isLessThanOrEqualTo3(2147483647).annotate({ expected: "a value less than or equal to 2147483647" })),
51619
51701
  isActive: Boolean3,
51620
51702
  createdAt: String4
51621
51703
  });
@@ -51644,7 +51726,7 @@ var EmailUpdateForwardingRuleRequestJson = Struct({
51644
51726
  preserveHeaders: optionalKey2(Union2([Boolean3, Null2])),
51645
51727
  appendNote: optionalKey2(Union2([String4, Null2])),
51646
51728
  conditions: optionalKey2(Union2([Union2([ForwardingConditions, Null2]), Null2])),
51647
- priority: optionalKey2(Union2([Finite.check(isFinite4().annotate({ expected: "a finite number" })), Null2])),
51729
+ priority: optionalKey2(Union2([Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })).check(isLessThanOrEqualTo3(2147483647).annotate({ expected: "a value less than or equal to 2147483647" })), Null2])),
51648
51730
  isActive: optionalKey2(Union2([Boolean3, Null2])),
51649
51731
  metadata: optionalKey2(Union2([Union2([Record(String4, JsonValue), Null2]), Null2]))
51650
51732
  });
@@ -51652,7 +51734,7 @@ var EmailUpdateForwardingRule200 = Struct({
51652
51734
  id: EmailForwardingRuleId,
51653
51735
  name: String4,
51654
51736
  forwardTo: ArraySchema(String4),
51655
- priority: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })),
51737
+ priority: Finite.check(isInt().annotate({ expected: "an integer" })).check(isGreaterThanOrEqualTo2(0).annotate({ expected: "a value greater than or equal to 0" })).check(isLessThanOrEqualTo3(2147483647).annotate({ expected: "a value less than or equal to 2147483647" })),
51656
51738
  isActive: Boolean3,
51657
51739
  updatedAt: String4
51658
51740
  });
@@ -56589,7 +56671,20 @@ var EmailGroup = class extends make38("Email").add(get5("emailListBlocks", "/v1/
56589
56671
  EmailCompleteCustomTrackingDomainConnect500,
56590
56672
  EmailCompleteCustomTrackingDomainConnect504.pipe(status(504))
56591
56673
  ]
56592
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.completeCustomTrackingDomainConnect").annotate(Summary, "Complete an automatic Custom Tracking Domain return").annotate(Description, "Validates the signed state returned by the DNS provider, then checks the tracking host again. Returning here never marks the host ready or changes which tracking host email uses."), post("emailSetDefaultDomain", "/v1/email/domains/:id/default", {
56674
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.completeCustomTrackingDomainConnect").annotate(Summary, "Complete an automatic Custom Tracking Domain return").annotate(Description, "Validates the signed state returned by the DNS provider, then checks the tracking host again. Returning here never marks the host ready or changes which tracking host email uses."), post("emailRecheckCustomTracking", "/v1/email/domains/:id/custom-tracking/recheck", {
56675
+ params: EmailRecheckCustomTrackingPathParams,
56676
+ success: EmailRecheckCustomTracking200,
56677
+ error: [
56678
+ EmailRecheckCustomTracking401.pipe(status(401)),
56679
+ EmailRecheckCustomTracking403.pipe(status(403)),
56680
+ EmailRecheckCustomTracking404.pipe(status(404)),
56681
+ EmailRecheckCustomTracking409.pipe(status(409)),
56682
+ EmailRecheckCustomTracking422.pipe(status(422)),
56683
+ EmailRecheckCustomTracking500,
56684
+ EmailRecheckCustomTracking502.pipe(status(502)),
56685
+ EmailRecheckCustomTracking504.pipe(status(504))
56686
+ ]
56687
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.recheckCustomTracking").annotate(Summary, "Recheck a custom tracking domain's certificate").annotate(Description, "Checks the hostname's certificate now instead of waiting for the next scheduled check, and returns the refreshed state. Samva checks a hostname at most once every 30 seconds; a recheck inside that window returns the state from the last check. Returns a conflict when custom tracking is disabled or being torn down."), post("emailSetDefaultDomain", "/v1/email/domains/:id/default", {
56593
56688
  params: EmailSetDefaultDomainPathParams,
56594
56689
  success: EmailSetDefaultDomain200,
56595
56690
  error: [
@@ -56661,7 +56756,7 @@ var EmailGroup = class extends make38("Email").add(get5("emailListBlocks", "/v1/
56661
56756
  EmailBuildDomainConnectApplyUrl502.pipe(status(502)),
56662
56757
  EmailBuildDomainConnectApplyUrl504.pipe(status(504))
56663
56758
  ]
56664
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.buildDomainConnectApplyUrl").annotate(Summary, "Build a one-click DNS apply URL").annotate(Description, "Builds the signed Domain Connect apply URL for the selected sending-only or sending-and-receiving service. Manual DNS remains available for either mode."), post("emailCompleteDomainConnect", "/v1/email/domains/:id/domain-connect/complete", {
56759
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.buildDomainConnectApplyUrl").annotate(Summary, "Build a one-click DNS apply URL").annotate(Description, "Builds the signed Domain Connect apply URL for the selected sending-only or sending-and-receiving service. The sending-and-receiving service writes the apex inbound MX, so it is refused until the domain's receiving is enabled. Manual DNS remains available for either mode."), post("emailCompleteDomainConnect", "/v1/email/domains/:id/domain-connect/complete", {
56665
56760
  params: EmailCompleteDomainConnectPathParams,
56666
56761
  payload: EmailCompleteDomainConnectRequestJson,
56667
56762
  success: EmailCompleteDomainConnect200,
@@ -56687,7 +56782,7 @@ var EmailGroup = class extends make38("Email").add(get5("emailListBlocks", "/v1/
56687
56782
  EmailEnableCatchAll502.pipe(status(502)),
56688
56783
  EmailEnableCatchAll504.pipe(status(504))
56689
56784
  ]
56690
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.enableCatchAll").annotate(Summary, "Enable catch-all receiving").annotate(Description, "Enables catch-all inbound receiving for a domain. `endpointId` is one of the organization's webhook endpoints; every address on the domain delivers `message.received` to it. Responds with 201."), post("emailEnableReceiving", "/v1/email/domains/:id/enable-receiving", {
56785
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.enableCatchAll").annotate(Summary, "Enable catch-all receiving").annotate(Description, "Enables catch-all inbound receiving for a verified domain. `endpointId` is one of the organization's webhook endpoints; every address on the domain delivers `message.received` to it. Omit it to deliver through forwarding rules alone, which needs at least one active forwarding rule covering the domain. Mail that matches neither a rule nor an endpoint is rejected as an unknown recipient. Responds with 201."), post("emailEnableReceiving", "/v1/email/domains/:id/enable-receiving", {
56691
56786
  params: EmailEnableReceivingPathParams,
56692
56787
  payload: EmailEnableReceivingRequestJson,
56693
56788
  success: EmailEnableReceiving200,
@@ -56699,7 +56794,18 @@ var EmailGroup = class extends make38("Email").add(get5("emailListBlocks", "/v1/
56699
56794
  EmailEnableReceiving500,
56700
56795
  EmailEnableReceiving504.pipe(status(504))
56701
56796
  ]
56702
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.enableReceiving").annotate(Summary, "Enable inbound email for a domain").annotate(Description, "Enables inbound receiving for a verified domain and returns the recipient addresses it accepts. Set `catchAll` to route every address on the domain; that requires a webhook endpoint, supplied as `endpointId` or already configured on the domain."), post("emailDetectRegistrar", "/v1/email/domains/:id/registrar", {
56797
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.enableReceiving").annotate(Summary, "Enable inbound email for a domain").annotate(Description, "Enables inbound receiving for a verified domain and returns the recipient addresses it accepts. Set `catchAll` to route every address on the domain; that needs a destination: a webhook endpoint, supplied as `endpointId` or already configured on the domain, or at least one active forwarding rule that covers the domain. Mail that matches neither is rejected, never accepted and dropped."), get5("emailGetReceivingStatus", "/v1/email/domains/:id/receiving-status", {
56798
+ params: EmailGetReceivingStatusPathParams,
56799
+ success: EmailGetReceivingStatus200,
56800
+ error: [
56801
+ EmailGetReceivingStatus401.pipe(status(401)),
56802
+ EmailGetReceivingStatus403.pipe(status(403)),
56803
+ EmailGetReceivingStatus404.pipe(status(404)),
56804
+ EmailGetReceivingStatus422.pipe(status(422)),
56805
+ EmailGetReceivingStatus500,
56806
+ EmailGetReceivingStatus504.pipe(status(504))
56807
+ ]
56808
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.getReceivingStatus").annotate(Summary, "Get receiving setup status").annotate(Description, "Reports each prerequisite for receiving a domain's mail at Samva, in order: sending verified, a destination (a webhook endpoint or an active forwarding rule), receiving enabled, and the inbound MX record. The MX answer is a live DNS lookup that names the current target when mail is routed elsewhere."), post("emailDetectRegistrar", "/v1/email/domains/:id/registrar", {
56703
56809
  params: EmailDetectRegistrarPathParams,
56704
56810
  payload: EmailDetectRegistrarRequestJson,
56705
56811
  success: EmailDetectRegistrar200,
@@ -56847,7 +56953,7 @@ var EmailGroup = class extends make38("Email").add(get5("emailListBlocks", "/v1/
56847
56953
  EmailListForwardingRules500,
56848
56954
  EmailListForwardingRules504.pipe(status(504))
56849
56955
  ]
56850
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.listForwardingRules").annotate(Summary, "List forwarding rules").annotate(Description, "Returns every inbound email forwarding rule, highest priority first, optionally filtered by domain and active state. An organization holds at most 100, so the list is never paged."), post("emailCreateForwardingRule", "/v1/email/forwarding", {
56956
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.listForwardingRules").annotate(Summary, "List forwarding rules").annotate(Description, "Returns every inbound email forwarding rule in the order inbound mail is matched against them: highest priority first, then an exact address before a wildcard pattern at equal priority, then oldest first. The first rule that matches is the one that forwards. Filtering by domain returns that domain's rules and the rules that apply to every domain (domainId null). An organization holds at most 100, so the list is never paged."), post("emailCreateForwardingRule", "/v1/email/forwarding", {
56851
56957
  payload: EmailCreateForwardingRuleRequestJson,
56852
56958
  success: EmailCreateForwardingRule201.pipe(status(201)),
56853
56959
  error: [
@@ -56859,7 +56965,7 @@ var EmailGroup = class extends make38("Email").add(get5("emailListBlocks", "/v1/
56859
56965
  EmailCreateForwardingRule500,
56860
56966
  EmailCreateForwardingRule504.pipe(status(504))
56861
56967
  ]
56862
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.createForwardingRule").annotate(Summary, "Create a forwarding rule").annotate(Description, "Creates an inbound email forwarding rule and returns it. Responds with 201. An organization holds at most 100 forwarding rules."), del("emailDeleteForwardingRule", "/v1/email/forwarding/:id", {
56968
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "email.createForwardingRule").annotate(Summary, "Create a forwarding rule").annotate(Description, "Creates an inbound email forwarding rule and returns it. Responds with 201. Priority is a non-negative integer, default 0; higher priority rules are tried first, and at equal priority an exact address outranks a wildcard pattern. An organization holds at most 100 forwarding rules."), del("emailDeleteForwardingRule", "/v1/email/forwarding/:id", {
56863
56969
  params: EmailDeleteForwardingRulePathParams,
56864
56970
  success: EmailDeleteForwardingRule200,
56865
56971
  error: [
@@ -59110,7 +59216,7 @@ var generateIdempotencyKey = gen2(function* () {
59110
59216
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
59111
59217
  });
59112
59218
 
59113
- // ../sdk/dist/operation-BuyvAsZ1.js
59219
+ // ../sdk/dist/operation-BEghYyLu.js
59114
59220
  var SDK_OPERATION_CALLS = {
59115
59221
  brandsList: { kind: "none" },
59116
59222
  brandsCreate: { kind: "payload" },
@@ -59308,6 +59414,10 @@ var SDK_OPERATION_CALLS = {
59308
59414
  kind: "pathPayload",
59309
59415
  paths: ["id"]
59310
59416
  },
59417
+ emailGetReceivingStatus: {
59418
+ kind: "path",
59419
+ paths: ["id"]
59420
+ },
59311
59421
  emailEnableCustomTracking: {
59312
59422
  kind: "pathPayload",
59313
59423
  paths: ["id"]
@@ -59316,6 +59426,10 @@ var SDK_OPERATION_CALLS = {
59316
59426
  kind: "path",
59317
59427
  paths: ["id"]
59318
59428
  },
59429
+ emailRecheckCustomTracking: {
59430
+ kind: "path",
59431
+ paths: ["id"]
59432
+ },
59319
59433
  emailGetCustomTrackingDomainConnect: {
59320
59434
  kind: "path",
59321
59435
  paths: ["id"]
@@ -60357,8 +60471,10 @@ var getDomainConnect = make40("emailGetDomainConnect");
60357
60471
  var buildDomainConnectApplyUrl = make40("emailBuildDomainConnectApplyUrl");
60358
60472
  var completeDomainConnect = make40("emailCompleteDomainConnect");
60359
60473
  var enableReceiving = make40("emailEnableReceiving");
60474
+ var getReceivingStatus = make40("emailGetReceivingStatus");
60360
60475
  var enableCustomTracking = make40("emailEnableCustomTracking");
60361
60476
  var getCustomTracking = make40("emailGetCustomTracking");
60477
+ var recheckCustomTracking = make40("emailRecheckCustomTracking");
60362
60478
  var getCustomTrackingDomainConnect = make40("emailGetCustomTrackingDomainConnect");
60363
60479
  var buildCustomTrackingDomainConnectApplyUrl = make40("emailBuildCustomTrackingDomainConnectApplyUrl");
60364
60480
  var completeCustomTrackingDomainConnect = make40("emailCompleteCustomTrackingDomainConnect");
@@ -60420,7 +60536,7 @@ var getVersion = make40("extensionsGetVersion");
60420
60536
  // ../sdk/dist/effect/feedback.js
60421
60537
  var send2 = make40("feedbackSend");
60422
60538
 
60423
- // ../sdk/dist/mailbox-events-CcWm8lO4.js
60539
+ // ../sdk/dist/mailbox-events-CsbF35vH.js
60424
60540
  var MAILBOX_EVENT_STREAM_PATH = "/v1/mailboxes/events";
60425
60541
  var MailboxEventStreamCloseCode = {
60426
60542
  invalidFrame: 4400,
@@ -60470,7 +60586,9 @@ var RETRY_MAX_MILLIS = 30000;
60470
60586
  var RETRY_AFTER_MAX_MILLIS = 300000;
60471
60587
  var REFUSAL_BODY_LIMIT = 65536;
60472
60588
  var DIAGNOSE_TIMEOUT_MILLIS = 1e4;
60473
- var MAILBOX_EVENT_STREAM_IDLE_MILLIS = 90000;
60589
+ var MAILBOX_EVENT_STREAM_PING_FRAME = '{"kind":"ping"}';
60590
+ var MAILBOX_EVENT_STREAM_PING_MILLIS = 30000;
60591
+ var MAILBOX_EVENT_STREAM_IDLE_MILLIS = 3 * MAILBOX_EVENT_STREAM_PING_MILLIS;
60474
60592
  var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
60475
60593
  var frameText = (raw) => {
60476
60594
  if (typeof raw === "string")
@@ -60488,9 +60606,11 @@ var decodeFrame = (raw) => {
60488
60606
  } catch {
60489
60607
  return;
60490
60608
  }
60609
+ if (isRecord(parsed) && parsed.kind === "pong")
60610
+ return { kind: "pong" };
60491
60611
  if (!isRecord(parsed) || typeof parsed.cursor !== "string")
60492
60612
  return;
60493
- if (parsed.kind === "live" || parsed.kind === "heartbeat")
60613
+ if (parsed.kind === "live" || parsed.kind === "progress")
60494
60614
  return {
60495
60615
  kind: parsed.kind,
60496
60616
  cursor: parsed.cursor
@@ -60831,6 +60951,7 @@ var MailboxEventStream = class {
60831
60951
  let opened = false;
60832
60952
  let settled = false;
60833
60953
  let idleTimer;
60954
+ let pingTimer;
60834
60955
  const armIdle = () => {
60835
60956
  clearTimeout(idleTimer);
60836
60957
  idleTimer = setTimeout(() => {
@@ -60846,6 +60967,7 @@ var MailboxEventStream = class {
60846
60967
  return;
60847
60968
  settled = true;
60848
60969
  clearTimeout(idleTimer);
60970
+ clearInterval(pingTimer);
60849
60971
  this.#stop.signal.removeEventListener("abort", onStop);
60850
60972
  if (this.#socket === socket)
60851
60973
  this.#socket = undefined;
@@ -60868,6 +60990,10 @@ var MailboxEventStream = class {
60868
60990
  armIdle();
60869
60991
  socket.addEventListener("open", () => {
60870
60992
  opened = true;
60993
+ pingTimer = setInterval(() => {
60994
+ if (socket.readyState === OPEN)
60995
+ socket.send(MAILBOX_EVENT_STREAM_PING_FRAME);
60996
+ }, MAILBOX_EVENT_STREAM_PING_MILLIS);
60871
60997
  });
60872
60998
  socket.addEventListener("message", (message) => {
60873
60999
  if (this.#ended)
@@ -60884,6 +61010,8 @@ var MailboxEventStream = class {
60884
61010
  socket.close(1000);
60885
61011
  return;
60886
61012
  }
61013
+ if (frame.kind === "pong")
61014
+ return;
60887
61015
  if (frame.kind === "event") {
60888
61016
  this.#queue.push({
60889
61017
  cursor: frame.cursor,
@@ -60994,7 +61122,7 @@ var readRefusalBody = async (response) => {
60994
61122
  }
60995
61123
  };
60996
61124
 
60997
- // ../sdk/dist/mailboxes-4gJMAJh4.js
61125
+ // ../sdk/dist/mailboxes-BtcktcNB.js
60998
61126
  var toStreamError = (cause) => cause instanceof MailboxEventStreamError ? cause : new MailboxEventStreamError({
60999
61127
  _tag: "StreamDisconnectedError",
61000
61128
  message: String(cause)
@@ -61412,7 +61540,7 @@ var commandTree = {
61412
61540
  check: {},
61413
61541
  remove: {},
61414
61542
  "rotate-dkim": {},
61415
- "custom-tracking": { enable: {}, status: {}, disable: {} }
61543
+ "custom-tracking": { enable: {}, status: {}, recheck: {}, disable: {} }
61416
61544
  },
61417
61545
  senders: { list: {}, add: {}, check: {}, remove: {} }
61418
61546
  },
@@ -63282,7 +63410,10 @@ var printCustomTracking = (data) => gen2(function* () {
63282
63410
  ["Host", custom.hostname],
63283
63411
  ["Region", custom.region],
63284
63412
  ["Certificate", custom.sslStatus ?? "pending"],
63285
- ["CAA blocked", custom.caaBlocked ? "yes" : "no"]
63413
+ ["CAA blocked", custom.caaBlocked ? "yes" : "no"],
63414
+ ["Reason", custom.certificate?.reason.message],
63415
+ ["Last checked", custom.certificate?.lastCheckedAt],
63416
+ ["Next check", custom.certificate?.nextCheckAt]
63286
63417
  ]);
63287
63418
  yield* log(`
63288
63419
  Create these DNS records:`);
@@ -63334,6 +63465,16 @@ var customStatus = make29("status", {
63334
63465
  return yield* printJson(data);
63335
63466
  yield* printCustomTracking(data);
63336
63467
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
63468
+ var customRecheck = make29("recheck", {
63469
+ id: String10("id").pipe(withDescription4("Domain id to recheck")),
63470
+ json: jsonFlag
63471
+ }).pipe(withDescription3("Check a domain's custom tracking certificate now instead of waiting for the next scheduled check"), withExamples([{ command: "samva email domains custom-tracking recheck dom_abc123" }]), withHandler(({ id, json }) => gen2(function* () {
63472
+ const client = yield* getClientProvider;
63473
+ const data = yield* recheckCustomTracking({ id }).pipe(client.provide, mapError2((error) => toCliApiError("Could not recheck custom tracking", error)));
63474
+ if (json)
63475
+ return yield* printJson(data);
63476
+ yield* printCustomTracking(data);
63477
+ }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
63337
63478
  var customDisable = make29("disable", {
63338
63479
  id: String10("id").pipe(withDescription4("Domain id to disable custom tracking for")),
63339
63480
  dryRun: Boolean6("dry-run").pipe(withDescription2("Print the teardown plan without calling the API"), withDefault5(false)),
@@ -63358,50 +63499,106 @@ var customDisable = make29("disable", {
63358
63499
  return yield* printJson(data);
63359
63500
  yield* log(`Disabled custom tracking for ${id}.`);
63360
63501
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
63361
- var customTracking = make29("custom-tracking").pipe(withDescription3("Manage a domain's custom tracking (custom open/click subdomain)"), withExamples([{ command: "samva email domains custom-tracking enable dom_abc123" }]), withSubcommands([customEnable, customStatus, customDisable]));
63502
+ var customTracking = make29("custom-tracking").pipe(withDescription3("Manage a domain's custom tracking (custom open/click subdomain)"), withExamples([{ command: "samva email domains custom-tracking enable dom_abc123" }]), withSubcommands([customEnable, customStatus, customRecheck, customDisable]));
63362
63503
  var domainsCommand = make29("domains").pipe(withDescription3("Manage email sending domains"), withExamples([
63363
63504
  { command: "samva email domains list", description: "List configured domains" }
63364
63505
  ]), withSubcommands([list21, add2, check2, rotateDkim, remove16, customTracking]));
63365
63506
 
63366
63507
  // src/commands/email/receiving.ts
63508
+ var destinationLabel = (destination) => {
63509
+ const parts = [
63510
+ destination.endpointId === null ? [] : [`webhook ${destination.endpointId}`],
63511
+ destination.forwardingRules === 0 ? [] : [
63512
+ `${destination.forwardingRules} forwarding rule${destination.forwardingRules === 1 ? "" : "s"}`
63513
+ ],
63514
+ destination.mailboxes === 0 ? [] : [`${destination.mailboxes} mailbox${destination.mailboxes === 1 ? "" : "es"}`]
63515
+ ].flat();
63516
+ return parts.length === 0 ? "none (add a webhook endpoint or a forwarding rule)" : parts.join(", ");
63517
+ };
63518
+ var mxLabel = (mx) => {
63519
+ switch (mx.state) {
63520
+ case "pointing-here":
63521
+ return `points at ${mx.expected.exchange}`;
63522
+ case "elsewhere":
63523
+ return `routes mail to ${mx.current[0]?.exchange ?? "another provider"}; set it to ${mx.expected.exchange}`;
63524
+ case "missing":
63525
+ return `no MX record; add ${mx.expected.exchange}`;
63526
+ default:
63527
+ return "could not be looked up; try again";
63528
+ }
63529
+ };
63367
63530
  var status2 = make29("status", {
63368
63531
  domainId: String10("domain-id").pipe(withDescription4("Sending domain id")),
63369
63532
  json: jsonFlag
63370
- }).pipe(withDescription3("Get a domain's receiving prerequisite status"), withExamples([{ command: "samva email receiving status dom_abc123 --json" }]), withHandler(({ domainId, json }) => gen2(function* () {
63533
+ }).pipe(withDescription3("Report each prerequisite for receiving a domain's mail at Samva, in order, with a live MX lookup"), withExamples([{ command: "samva email receiving status dom_abc123 --json" }]), withHandler(({ domainId, json }) => gen2(function* () {
63371
63534
  const client = yield* getClientProvider;
63372
- const data = yield* getDomainVerification({ id: domainId }).pipe(client.provide, mapError2((error) => toCliApiError("Could not get receiving status", error)));
63535
+ const data = yield* getReceivingStatus({ id: domainId }).pipe(client.provide, mapError2((error) => toCliApiError("Could not get receiving status", error)));
63373
63536
  if (json)
63374
63537
  return yield* printJson(data);
63538
+ const mark = (satisfied) => satisfied ? "done" : "todo";
63375
63539
  yield* printFields([
63376
63540
  ["Domain", data.domain],
63377
- ["Phase", data.phase]
63541
+ ["1. Sending", `${mark(data.sending.satisfied)} (${data.sending.phase})`],
63542
+ [
63543
+ "2. Destination",
63544
+ `${mark(data.destination.satisfied)} ${destinationLabel(data.destination)}`
63545
+ ],
63546
+ [
63547
+ "3. Receiving",
63548
+ `${mark(data.receiving.satisfied)}${data.receiving.catchAll ? " (catch-all)" : ""}`
63549
+ ],
63550
+ [`4. Inbound MX`, `${mark(data.mx.satisfied)} ${data.receivingHost} ${mxLabel(data.mx)}`],
63551
+ ["Ready", data.ready ? "yes" : "no"]
63378
63552
  ]);
63379
63553
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
63380
63554
  var enable2 = make29("enable", {
63381
63555
  domainId: String10("domain-id").pipe(withDescription4("Sending domain id")),
63382
63556
  domain: String9("domain").pipe(withDescription2("Verified domain name")),
63383
- address: String9("address").pipe(withDescription2("Recipient address to receive (repeatable)"), atLeast2(1)),
63384
- catchAll: Boolean6("catch-all").pipe(withDescription2("Also receive unmatched addresses"), withDefault5(false)),
63557
+ address: String9("address").pipe(withDescription2("Exact recipient address to receive (repeatable)"), atLeast2(0)),
63558
+ catchAll: Boolean6("catch-all").pipe(withDescription2("Receive every address on the domain. Needs --endpoint or an active forwarding rule that covers the domain"), withDefault5(false)),
63559
+ endpoint: String9("endpoint").pipe(withDescription2("Webhook endpoint id that receives message.received for a catch-all domain"), optional4),
63385
63560
  dryRun: Boolean6("dry-run").pipe(withDescription2("Print the provider setup request without calling the API"), withDefault5(false)),
63386
63561
  json: jsonFlag
63387
- }).pipe(withDescription3("Enable inbound receiving for a verified domain"), withExamples([
63562
+ }).pipe(withDescription3("Enable inbound receiving for a verified domain. Receiving needs somewhere to deliver: exact addresses are accepted, and a catch-all needs a webhook endpoint or a forwarding rule"), withExamples([
63563
+ {
63564
+ command: "samva email receiving enable dom_abc123 --domain example.com --catch-all --endpoint wep_abc123"
63565
+ },
63566
+ {
63567
+ command: "samva email receiving enable dom_abc123 --domain example.com --catch-all"
63568
+ },
63388
63569
  {
63389
63570
  command: "samva email receiving enable dom_abc123 --domain example.com --address replies@example.com --dry-run"
63390
63571
  },
63391
63572
  {
63392
63573
  command: "samva email receiving enable dom_abc123 --domain example.com --address replies@example.com"
63393
63574
  }
63394
- ]), withHandler(({ domainId, domain, address, catchAll, dryRun, json }) => gen2(function* () {
63395
- const request = { domain, catchAll, addresses: [...address] };
63575
+ ]), withHandler(({ domainId, domain, address, catchAll, endpoint, dryRun, json }) => gen2(function* () {
63576
+ if (address.length === 0 && !catchAll) {
63577
+ return yield* new SamvaCliError({
63578
+ message: "Pass --address for each exact recipient, or --catch-all for the whole domain."
63579
+ });
63580
+ }
63581
+ const endpointId = getOrUndefined(endpoint);
63582
+ if (endpointId !== undefined && !catchAll) {
63583
+ return yield* new SamvaCliError({
63584
+ message: "--endpoint names the catch-all webhook, so it needs --catch-all."
63585
+ });
63586
+ }
63587
+ const request = {
63588
+ domain,
63589
+ catchAll,
63590
+ addresses: [...address],
63591
+ ...endpointId === undefined ? {} : { endpointId }
63592
+ };
63396
63593
  if (dryRun)
63397
- return yield* json ? printJson({ dryRun: true, action: "email.receiving.enable", domainId, request }) : log(`[dry-run] Would enable receiving for ${domain} (${address.join(", ")}).`);
63594
+ return yield* json ? printJson({ dryRun: true, action: "email.receiving.enable", domainId, request }) : log(`[dry-run] Would enable receiving for ${domain} (${catchAll ? "catch-all" : address.join(", ")}).`);
63398
63595
  const client = yield* getClientProvider;
63399
63596
  const data = yield* enableReceiving({ id: domainId, ...request }).pipe(client.provide, mapError2((error) => toCliApiError("Could not enable receiving", error)));
63400
63597
  if (json)
63401
63598
  return yield* printJson(data);
63402
63599
  yield* printFields([["Recipients", data.recipients.join(", ")]]);
63403
63600
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
63404
- var receivingCommand = make29("receiving").pipe(withDescription3("Configure inbound email receiving"), withExamples([{ command: "samva email receiving status dom_abc123" }]), withSubcommands([status2, enable2]));
63601
+ var receivingCommand = make29("receiving").pipe(withDescription3("Move a domain's inbound email to Samva and check what is left to do"), withExamples([{ command: "samva email receiving status dom_abc123" }]), withSubcommands([status2, enable2]));
63405
63602
 
63406
63603
  // src/commands/email/review.ts
63407
63604
  var readStatus = gen2(function* () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@samva/cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Samva CLI — email and platform operations from your terminal",
5
5
  "keywords": [
6
6
  "cli",
@@ -49,22 +49,22 @@
49
49
  "@effect/platform-bun": "4.0.0",
50
50
  "@effect/vitest": "4.0.0",
51
51
  "@samva/core": "0.0.0",
52
- "@samva/mailbox": "0.1.1",
52
+ "@samva/mailbox": "0.1.2",
53
53
  "@samva/markup": "^0.12.0",
54
54
  "@samva/vite": "^0.12.0",
55
55
  "@types/node": "^26.6.2",
56
56
  "better-auth": "1.7.5",
57
57
  "effect": "4.0.0",
58
- "samva": "0.11.0",
58
+ "samva": "0.12.0",
59
59
  "typescript": "^7.0.2",
60
60
  "vite": "~8.3.0",
61
61
  "vitest": "^5.0.3"
62
62
  },
63
63
  "optionalDependencies": {
64
- "@samva/cli-darwin-arm64": "0.8.0",
65
- "@samva/cli-darwin-x64": "0.8.0",
66
- "@samva/cli-linux-arm64": "0.8.0",
67
- "@samva/cli-linux-x64": "0.8.0"
64
+ "@samva/cli-darwin-arm64": "0.9.0",
65
+ "@samva/cli-darwin-x64": "0.9.0",
66
+ "@samva/cli-linux-arm64": "0.9.0",
67
+ "@samva/cli-linux-x64": "0.9.0"
68
68
  },
69
69
  "engines": {
70
70
  "node": ">=18"