@easybits.cloud/mailmask 0.6.1 → 0.7.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/index.cjs CHANGED
@@ -21,11 +21,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  AuthError: () => AuthError,
24
+ BALANCE_LOW_FRACTION: () => BALANCE_LOW_FRACTION,
25
+ BALANCE_MOVEMENT_KINDS: () => BALANCE_MOVEMENT_KINDS,
24
26
  ConflictError: () => ConflictError,
25
27
  DOMAIN_CLAIM_TTL_DAYS: () => DOMAIN_CLAIM_TTL_DAYS,
26
28
  EXTERNAL_ID_RE: () => EXTERNAL_ID_RE,
27
29
  FALLBACK_LABEL_RE: () => FALLBACK_LABEL_RE,
28
30
  ForbiddenError: () => ForbiddenError,
31
+ InsufficientBalanceError: () => InsufficientBalanceError,
29
32
  MAX_BATCH_RECIPIENTS: () => MAX_BATCH_RECIPIENTS,
30
33
  MAX_TAGS: () => MAX_TAGS,
31
34
  MAX_TEMPLATE_VARS: () => MAX_TEMPLATE_VARS,
@@ -42,8 +45,12 @@ __export(index_exports, {
42
45
  PARTNER_API_PREFIX: () => PARTNER_API_PREFIX,
43
46
  PARTNER_ERROR_STATUS: () => PARTNER_ERROR_STATUS,
44
47
  PARTNER_HEADERS: () => PARTNER_HEADERS,
48
+ PARTNER_PRICE_PER_EMAIL_CENTS: () => PARTNER_PRICE_PER_EMAIL_CENTS,
45
49
  PARTNER_ROUTES: () => PARTNER_ROUTES,
46
50
  PARTNER_SIGNATURE_TOLERANCE_S: () => PARTNER_SIGNATURE_TOLERANCE_S,
51
+ PARTNER_TENANT_MONTH_CENTS: () => PARTNER_TENANT_MONTH_CENTS,
52
+ PARTNER_TOPUP_MAX_CENTS: () => PARTNER_TOPUP_MAX_CENTS,
53
+ PARTNER_TOPUP_MIN_CENTS: () => PARTNER_TOPUP_MIN_CENTS,
47
54
  PARTNER_WEBHOOK_EVENTS: () => PARTNER_WEBHOOK_EVENTS,
48
55
  PartnerApiError: () => PartnerApiError,
49
56
  PausedError: () => PausedError,
@@ -716,6 +723,11 @@ var PARTNER_ERROR_STATUS = {
716
723
  recipient_suppressed: 422,
717
724
  /** El tenant está en pausa (por MailMask o por el partner). No se manda nada hasta `resume`. */
718
725
  tenant_paused: 423,
726
+ /**
727
+ * Saldo prepagado insuficiente (con `balanceCents` y `requiredCents`). Sólo cuando MailMask
728
+ * activó el cobro por adelantado del partner. No se reintenta: recarga con `POST /topup`.
729
+ */
730
+ insufficient_balance: 402,
719
731
  /** Cuota mensual del tenant agotada. */
720
732
  quota_exceeded: 429,
721
733
  /** Demasiadas peticiones; reintenta tras `Retry-After`. */
@@ -795,6 +807,12 @@ function personalizePartnerMessage(input) {
795
807
  ...text !== void 0 ? { text: renderTemplate(text, vars) } : {}
796
808
  };
797
809
  }
810
+ var PARTNER_PRICE_PER_EMAIL_CENTS = 6;
811
+ var PARTNER_TENANT_MONTH_CENTS = 4900;
812
+ var PARTNER_TOPUP_MIN_CENTS = 5e4;
813
+ var PARTNER_TOPUP_MAX_CENTS = 1e7;
814
+ var BALANCE_LOW_FRACTION = 0.2;
815
+ var BALANCE_MOVEMENT_KINDS = ["topup", "send", "tenant_fee", "refund", "adjust"];
798
816
  var PARTNER_WEBHOOK_EVENTS = [
799
817
  "email.sent",
800
818
  "email.delivered",
@@ -804,7 +822,8 @@ var PARTNER_WEBHOOK_EVENTS = [
804
822
  "contact.unsubscribed",
805
823
  "tenant.paused",
806
824
  "tenant.resumed",
807
- "domain.verified"
825
+ "domain.verified",
826
+ "balance.low"
808
827
  ];
809
828
  var WEBHOOK_HEADERS = {
810
829
  event: "X-MailMask-Event",
@@ -836,6 +855,9 @@ var PARTNER_ROUTES = {
836
855
  batch: { method: "POST", path: "/tenants/:externalId/batch" },
837
856
  getBatch: { method: "GET", path: "/tenants/:externalId/batch/:batchId" },
838
857
  usage: { method: "GET", path: "/usage" },
858
+ balance: { method: "GET", path: "/balance" },
859
+ balanceMovements: { method: "GET", path: "/balance/movements" },
860
+ topup: { method: "POST", path: "/topup" },
839
861
  getWebhook: { method: "GET", path: "/webhook" },
840
862
  setWebhook: { method: "PUT", path: "/webhook" },
841
863
  testWebhook: { method: "POST", path: "/webhook/test" },
@@ -851,6 +873,14 @@ var PARTNER_ADMIN_ROUTES = {
851
873
  setFallbackZone: { method: "PUT", path: "/api/partners/:partnerId/fallback-zone" },
852
874
  /** La zona de respaldo y si la delegación ya se ve en el DNS público. */
853
875
  getFallbackZone: { method: "GET", path: "/api/partners/:partnerId/fallback-zone" },
876
+ /** El saldo del partner (dueño o MailMask). */
877
+ getBalance: { method: "GET", path: "/api/partners/:partnerId/balance" },
878
+ /** Sólo MailMask (admin): abono o cargo manual (`adjust`). */
879
+ adjustBalance: { method: "POST", path: "/api/partners/:partnerId/balance/adjust" },
880
+ /** Sólo MailMask (admin): prende o apaga el cobro por adelantado. */
881
+ setPrepaidEnforced: { method: "PUT", path: "/api/partners/:partnerId/prepaid" },
882
+ /** Recarga con MercadoPago desde la sesión del dueño (misma respuesta que `POST /topup`). */
883
+ topup: { method: "POST", path: "/api/partners/:partnerId/topup" },
854
884
  listCredentials: { method: "GET", path: "/api/partners/:partnerId/credentials" },
855
885
  createCredential: { method: "POST", path: "/api/partners/:partnerId/credentials" },
856
886
  rotateCredential: { method: "POST", path: "/api/partners/:partnerId/credentials/:keyId/rotate" },
@@ -968,6 +998,19 @@ var RateLimitError = class extends QuotaError {
968
998
  this.kind = "rate_limit";
969
999
  }
970
1000
  };
1001
+ var InsufficientBalanceError = class extends PartnerApiError {
1002
+ constructor() {
1003
+ super(...arguments);
1004
+ this.name = "InsufficientBalanceError";
1005
+ this.kind = "insufficient_balance";
1006
+ }
1007
+ get balanceCents() {
1008
+ return this.body.balanceCents;
1009
+ }
1010
+ get requiredCents() {
1011
+ return this.body.requiredCents;
1012
+ }
1013
+ };
971
1014
  var NetworkError = class extends PartnerApiError {
972
1015
  constructor(message, cause) {
973
1016
  super(0, message, {}, { cause });
@@ -990,6 +1033,8 @@ function errorFor(status, body, fallback, meta = {}) {
990
1033
  switch (status) {
991
1034
  case 401:
992
1035
  return new AuthError(status, msg, body, meta);
1036
+ case 402:
1037
+ return new InsufficientBalanceError(status, msg, body, meta);
993
1038
  case 403:
994
1039
  return new ForbiddenError(status, msg, body, meta);
995
1040
  case 404:
@@ -1190,6 +1235,26 @@ var MailMaskPartner = class {
1190
1235
  async batchStatus(externalId, batchId) {
1191
1236
  return (await this.request("GET", routePath(PARTNER_ROUTES.getBatch.path, { externalId, batchId }))).data;
1192
1237
  }
1238
+ /** Saldo prepagado: saldo, precios, última recarga y consumo del mes (centavos MXN). */
1239
+ async balance() {
1240
+ return (await this.request("GET", PARTNER_ROUTES.balance.path)).data;
1241
+ }
1242
+ /** Movimientos del saldo, del más nuevo al más viejo; pagina con `nextCursor`. */
1243
+ async balanceMovements(query = {}) {
1244
+ const q = new URLSearchParams();
1245
+ if (query.cursor) q.set("cursor", query.cursor);
1246
+ if (query.limit !== void 0) q.set("limit", String(query.limit));
1247
+ if (query.kind?.length) q.set("kind", query.kind.join(","));
1248
+ const qs = q.toString();
1249
+ return (await this.request("GET", `${PARTNER_ROUTES.balanceMovements.path}${qs ? `?${qs}` : ""}`)).data;
1250
+ }
1251
+ /**
1252
+ * Liga de MercadoPago para recargar saldo (pago único, $500 a $100,000 MXN). El saldo sube cuando
1253
+ * MercadoPago confirma el pago, no al crear la liga. Abre `initPoint` en el navegador de quien paga.
1254
+ */
1255
+ async topup(input) {
1256
+ return (await this.request("POST", PARTNER_ROUTES.topup.path, input)).data;
1257
+ }
1193
1258
  /** Uso del mes (`YYYY-MM`, default el mes en curso UTC): tenants activos y envíos. */
1194
1259
  async usage(opts = {}) {
1195
1260
  const q = opts.month ? `?month=${encodeURIComponent(opts.month)}` : "";
@@ -1405,6 +1470,22 @@ var MailMaskPartnerAdmin = class {
1405
1470
  getFallbackZone(partnerId) {
1406
1471
  return this.req("getFallbackZone", { partnerId });
1407
1472
  }
1473
+ /** Saldo del partner con sus últimos 20 movimientos (dueño o MailMask). */
1474
+ getBalance(partnerId) {
1475
+ return this.req("getBalance", { partnerId });
1476
+ }
1477
+ /** Sólo MailMask (admin): abono (o cargo, negativo) manual. Con la misma `idempotencyKey` no se repite. */
1478
+ adjustBalance(partnerId, input) {
1479
+ return this.req("adjustBalance", { partnerId }, input);
1480
+ }
1481
+ /** Sólo MailMask (admin): prende o apaga el cobro por adelantado del partner. */
1482
+ setPrepaidEnforced(partnerId, enforced) {
1483
+ return this.req("setPrepaidEnforced", { partnerId }, { enforced });
1484
+ }
1485
+ /** Liga de MercadoPago para recargar, con la llave del dueño. */
1486
+ topup(partnerId, input) {
1487
+ return this.req("topup", { partnerId }, input);
1488
+ }
1408
1489
  listCredentials(partnerId) {
1409
1490
  return this.req("listCredentials", { partnerId });
1410
1491
  }
@@ -1424,11 +1505,14 @@ var MailMaskPartnerAdmin = class {
1424
1505
  // Annotate the CommonJS export names for ESM import in node:
1425
1506
  0 && (module.exports = {
1426
1507
  AuthError,
1508
+ BALANCE_LOW_FRACTION,
1509
+ BALANCE_MOVEMENT_KINDS,
1427
1510
  ConflictError,
1428
1511
  DOMAIN_CLAIM_TTL_DAYS,
1429
1512
  EXTERNAL_ID_RE,
1430
1513
  FALLBACK_LABEL_RE,
1431
1514
  ForbiddenError,
1515
+ InsufficientBalanceError,
1432
1516
  MAX_BATCH_RECIPIENTS,
1433
1517
  MAX_TAGS,
1434
1518
  MAX_TEMPLATE_VARS,
@@ -1445,8 +1529,12 @@ var MailMaskPartnerAdmin = class {
1445
1529
  PARTNER_API_PREFIX,
1446
1530
  PARTNER_ERROR_STATUS,
1447
1531
  PARTNER_HEADERS,
1532
+ PARTNER_PRICE_PER_EMAIL_CENTS,
1448
1533
  PARTNER_ROUTES,
1449
1534
  PARTNER_SIGNATURE_TOLERANCE_S,
1535
+ PARTNER_TENANT_MONTH_CENTS,
1536
+ PARTNER_TOPUP_MAX_CENTS,
1537
+ PARTNER_TOPUP_MIN_CENTS,
1450
1538
  PARTNER_WEBHOOK_EVENTS,
1451
1539
  PartnerApiError,
1452
1540
  PausedError,
package/dist/index.js CHANGED
@@ -637,6 +637,11 @@ var PARTNER_ERROR_STATUS = {
637
637
  recipient_suppressed: 422,
638
638
  /** El tenant está en pausa (por MailMask o por el partner). No se manda nada hasta `resume`. */
639
639
  tenant_paused: 423,
640
+ /**
641
+ * Saldo prepagado insuficiente (con `balanceCents` y `requiredCents`). Sólo cuando MailMask
642
+ * activó el cobro por adelantado del partner. No se reintenta: recarga con `POST /topup`.
643
+ */
644
+ insufficient_balance: 402,
640
645
  /** Cuota mensual del tenant agotada. */
641
646
  quota_exceeded: 429,
642
647
  /** Demasiadas peticiones; reintenta tras `Retry-After`. */
@@ -716,6 +721,12 @@ function personalizePartnerMessage(input) {
716
721
  ...text !== void 0 ? { text: renderTemplate(text, vars) } : {}
717
722
  };
718
723
  }
724
+ var PARTNER_PRICE_PER_EMAIL_CENTS = 6;
725
+ var PARTNER_TENANT_MONTH_CENTS = 4900;
726
+ var PARTNER_TOPUP_MIN_CENTS = 5e4;
727
+ var PARTNER_TOPUP_MAX_CENTS = 1e7;
728
+ var BALANCE_LOW_FRACTION = 0.2;
729
+ var BALANCE_MOVEMENT_KINDS = ["topup", "send", "tenant_fee", "refund", "adjust"];
719
730
  var PARTNER_WEBHOOK_EVENTS = [
720
731
  "email.sent",
721
732
  "email.delivered",
@@ -725,7 +736,8 @@ var PARTNER_WEBHOOK_EVENTS = [
725
736
  "contact.unsubscribed",
726
737
  "tenant.paused",
727
738
  "tenant.resumed",
728
- "domain.verified"
739
+ "domain.verified",
740
+ "balance.low"
729
741
  ];
730
742
  var WEBHOOK_HEADERS = {
731
743
  event: "X-MailMask-Event",
@@ -757,6 +769,9 @@ var PARTNER_ROUTES = {
757
769
  batch: { method: "POST", path: "/tenants/:externalId/batch" },
758
770
  getBatch: { method: "GET", path: "/tenants/:externalId/batch/:batchId" },
759
771
  usage: { method: "GET", path: "/usage" },
772
+ balance: { method: "GET", path: "/balance" },
773
+ balanceMovements: { method: "GET", path: "/balance/movements" },
774
+ topup: { method: "POST", path: "/topup" },
760
775
  getWebhook: { method: "GET", path: "/webhook" },
761
776
  setWebhook: { method: "PUT", path: "/webhook" },
762
777
  testWebhook: { method: "POST", path: "/webhook/test" },
@@ -772,6 +787,14 @@ var PARTNER_ADMIN_ROUTES = {
772
787
  setFallbackZone: { method: "PUT", path: "/api/partners/:partnerId/fallback-zone" },
773
788
  /** La zona de respaldo y si la delegación ya se ve en el DNS público. */
774
789
  getFallbackZone: { method: "GET", path: "/api/partners/:partnerId/fallback-zone" },
790
+ /** El saldo del partner (dueño o MailMask). */
791
+ getBalance: { method: "GET", path: "/api/partners/:partnerId/balance" },
792
+ /** Sólo MailMask (admin): abono o cargo manual (`adjust`). */
793
+ adjustBalance: { method: "POST", path: "/api/partners/:partnerId/balance/adjust" },
794
+ /** Sólo MailMask (admin): prende o apaga el cobro por adelantado. */
795
+ setPrepaidEnforced: { method: "PUT", path: "/api/partners/:partnerId/prepaid" },
796
+ /** Recarga con MercadoPago desde la sesión del dueño (misma respuesta que `POST /topup`). */
797
+ topup: { method: "POST", path: "/api/partners/:partnerId/topup" },
775
798
  listCredentials: { method: "GET", path: "/api/partners/:partnerId/credentials" },
776
799
  createCredential: { method: "POST", path: "/api/partners/:partnerId/credentials" },
777
800
  rotateCredential: { method: "POST", path: "/api/partners/:partnerId/credentials/:keyId/rotate" },
@@ -889,6 +912,19 @@ var RateLimitError = class extends QuotaError {
889
912
  this.kind = "rate_limit";
890
913
  }
891
914
  };
915
+ var InsufficientBalanceError = class extends PartnerApiError {
916
+ constructor() {
917
+ super(...arguments);
918
+ this.name = "InsufficientBalanceError";
919
+ this.kind = "insufficient_balance";
920
+ }
921
+ get balanceCents() {
922
+ return this.body.balanceCents;
923
+ }
924
+ get requiredCents() {
925
+ return this.body.requiredCents;
926
+ }
927
+ };
892
928
  var NetworkError = class extends PartnerApiError {
893
929
  constructor(message, cause) {
894
930
  super(0, message, {}, { cause });
@@ -911,6 +947,8 @@ function errorFor(status, body, fallback, meta = {}) {
911
947
  switch (status) {
912
948
  case 401:
913
949
  return new AuthError(status, msg, body, meta);
950
+ case 402:
951
+ return new InsufficientBalanceError(status, msg, body, meta);
914
952
  case 403:
915
953
  return new ForbiddenError(status, msg, body, meta);
916
954
  case 404:
@@ -1111,6 +1149,26 @@ var MailMaskPartner = class {
1111
1149
  async batchStatus(externalId, batchId) {
1112
1150
  return (await this.request("GET", routePath(PARTNER_ROUTES.getBatch.path, { externalId, batchId }))).data;
1113
1151
  }
1152
+ /** Saldo prepagado: saldo, precios, última recarga y consumo del mes (centavos MXN). */
1153
+ async balance() {
1154
+ return (await this.request("GET", PARTNER_ROUTES.balance.path)).data;
1155
+ }
1156
+ /** Movimientos del saldo, del más nuevo al más viejo; pagina con `nextCursor`. */
1157
+ async balanceMovements(query = {}) {
1158
+ const q = new URLSearchParams();
1159
+ if (query.cursor) q.set("cursor", query.cursor);
1160
+ if (query.limit !== void 0) q.set("limit", String(query.limit));
1161
+ if (query.kind?.length) q.set("kind", query.kind.join(","));
1162
+ const qs = q.toString();
1163
+ return (await this.request("GET", `${PARTNER_ROUTES.balanceMovements.path}${qs ? `?${qs}` : ""}`)).data;
1164
+ }
1165
+ /**
1166
+ * Liga de MercadoPago para recargar saldo (pago único, $500 a $100,000 MXN). El saldo sube cuando
1167
+ * MercadoPago confirma el pago, no al crear la liga. Abre `initPoint` en el navegador de quien paga.
1168
+ */
1169
+ async topup(input) {
1170
+ return (await this.request("POST", PARTNER_ROUTES.topup.path, input)).data;
1171
+ }
1114
1172
  /** Uso del mes (`YYYY-MM`, default el mes en curso UTC): tenants activos y envíos. */
1115
1173
  async usage(opts = {}) {
1116
1174
  const q = opts.month ? `?month=${encodeURIComponent(opts.month)}` : "";
@@ -1326,6 +1384,22 @@ var MailMaskPartnerAdmin = class {
1326
1384
  getFallbackZone(partnerId) {
1327
1385
  return this.req("getFallbackZone", { partnerId });
1328
1386
  }
1387
+ /** Saldo del partner con sus últimos 20 movimientos (dueño o MailMask). */
1388
+ getBalance(partnerId) {
1389
+ return this.req("getBalance", { partnerId });
1390
+ }
1391
+ /** Sólo MailMask (admin): abono (o cargo, negativo) manual. Con la misma `idempotencyKey` no se repite. */
1392
+ adjustBalance(partnerId, input) {
1393
+ return this.req("adjustBalance", { partnerId }, input);
1394
+ }
1395
+ /** Sólo MailMask (admin): prende o apaga el cobro por adelantado del partner. */
1396
+ setPrepaidEnforced(partnerId, enforced) {
1397
+ return this.req("setPrepaidEnforced", { partnerId }, { enforced });
1398
+ }
1399
+ /** Liga de MercadoPago para recargar, con la llave del dueño. */
1400
+ topup(partnerId, input) {
1401
+ return this.req("topup", { partnerId }, input);
1402
+ }
1329
1403
  listCredentials(partnerId) {
1330
1404
  return this.req("listCredentials", { partnerId });
1331
1405
  }
@@ -1344,11 +1418,14 @@ var MailMaskPartnerAdmin = class {
1344
1418
  };
1345
1419
  export {
1346
1420
  AuthError,
1421
+ BALANCE_LOW_FRACTION,
1422
+ BALANCE_MOVEMENT_KINDS,
1347
1423
  ConflictError,
1348
1424
  DOMAIN_CLAIM_TTL_DAYS,
1349
1425
  EXTERNAL_ID_RE,
1350
1426
  FALLBACK_LABEL_RE,
1351
1427
  ForbiddenError,
1428
+ InsufficientBalanceError,
1352
1429
  MAX_BATCH_RECIPIENTS,
1353
1430
  MAX_TAGS,
1354
1431
  MAX_TEMPLATE_VARS,
@@ -1365,8 +1442,12 @@ export {
1365
1442
  PARTNER_API_PREFIX,
1366
1443
  PARTNER_ERROR_STATUS,
1367
1444
  PARTNER_HEADERS,
1445
+ PARTNER_PRICE_PER_EMAIL_CENTS,
1368
1446
  PARTNER_ROUTES,
1369
1447
  PARTNER_SIGNATURE_TOLERANCE_S,
1448
+ PARTNER_TENANT_MONTH_CENTS,
1449
+ PARTNER_TOPUP_MAX_CENTS,
1450
+ PARTNER_TOPUP_MIN_CENTS,
1370
1451
  PARTNER_WEBHOOK_EVENTS,
1371
1452
  PartnerApiError,
1372
1453
  PausedError,
@@ -59,6 +59,11 @@ export declare const PARTNER_ERROR_STATUS: {
59
59
  readonly recipient_suppressed: 422;
60
60
  /** El tenant está en pausa (por MailMask o por el partner). No se manda nada hasta `resume`. */
61
61
  readonly tenant_paused: 423;
62
+ /**
63
+ * Saldo prepagado insuficiente (con `balanceCents` y `requiredCents`). Sólo cuando MailMask
64
+ * activó el cobro por adelantado del partner. No se reintenta: recarga con `POST /topup`.
65
+ */
66
+ readonly insufficient_balance: 402;
62
67
  /** Cuota mensual del tenant agotada. */
63
68
  readonly quota_exceeded: 429;
64
69
  /** Demasiadas peticiones; reintenta tras `Retry-After`. */
@@ -75,6 +80,9 @@ export interface PartnerErrorBody {
75
80
  /** Sólo en `quota_exceeded`: el tope del mes y cuándo se reinicia (ISO, día 1 a las 00:00 UTC). */
76
81
  limit?: number;
77
82
  resetsAt?: string;
83
+ /** Sólo en `insufficient_balance`: saldo actual y lo que pedía el envío (centavos MXN). */
84
+ balanceCents?: number;
85
+ requiredCents?: number;
78
86
  /** Sólo en `recipient_suppressed`. */
79
87
  reason?: SuppressionReason;
80
88
  stream?: MessageStream;
@@ -368,6 +376,113 @@ export interface UsageResponse {
368
376
  sent: number;
369
377
  tenants: TenantUsage[];
370
378
  }
379
+ /**
380
+ * El partner paga por adelantado: cada correo y la cuota mensual de cada tenant activo se
381
+ * descuentan de un saldo que se recarga con MercadoPago (`POST /topup`). Todo en centavos de MXN.
382
+ */
383
+ /** Precio por correo enviado (por destinatario). */
384
+ export declare const PARTNER_PRICE_PER_EMAIL_CENTS = 6;
385
+ /**
386
+ * Cuota mensual por tenant activo. Activo = envía ese mes: se descuenta con su primer envío del
387
+ * mes (UTC), una sola vez, así que un tenant dormido no cuesta nada.
388
+ */
389
+ export declare const PARTNER_TENANT_MONTH_CENTS = 4900;
390
+ /** Recarga mínima y máxima por pago. */
391
+ export declare const PARTNER_TOPUP_MIN_CENTS = 50000;
392
+ export declare const PARTNER_TOPUP_MAX_CENTS = 10000000;
393
+ /** `balance.low` sale cuando el saldo baja del 20 % de la última recarga (una vez por recarga). */
394
+ export declare const BALANCE_LOW_FRACTION = 0.2;
395
+ export type BalanceMovementKind = "topup" | "send" | "tenant_fee" | "refund" | "adjust";
396
+ export declare const BALANCE_MOVEMENT_KINDS: readonly BalanceMovementKind[];
397
+ export interface BalanceMovement {
398
+ id: number;
399
+ kind: BalanceMovementKind;
400
+ /** Positivo = abono (recarga, devolución, ajuste a favor); negativo = cargo. */
401
+ amountCents: number;
402
+ balanceAfterCents: number;
403
+ /** Tenant del cargo (`send`, `tenant_fee`, `refund`); null en recargas y ajustes. */
404
+ externalId: string | null;
405
+ /** Referencia: `batch:<id>`, mes de la cuota (`YYYY-MM`), pago de MercadoPago… */
406
+ ref: string | null;
407
+ note: string | null;
408
+ createdAt: string;
409
+ }
410
+ /** `GET /balance` */
411
+ export interface BalanceResponse {
412
+ currency: "MXN";
413
+ balanceCents: number;
414
+ /**
415
+ * true = sin saldo suficiente los envíos se rechazan (402 `insufficient_balance`) y los lotes
416
+ * esperan. false = el saldo se lleva igual (puede quedar negativo) pero no frena nada.
417
+ */
418
+ enforced: boolean;
419
+ prices: {
420
+ perEmailCents: number;
421
+ tenantMonthCents: number;
422
+ topupMinCents: number;
423
+ };
424
+ lastTopup: {
425
+ amountCents: number;
426
+ at: string;
427
+ } | null;
428
+ /** Debajo de esto sale `balance.low`; null sin recargas. */
429
+ lowBalanceThresholdCents: number | null;
430
+ /** Mes en curso (UTC). */
431
+ month: string;
432
+ /** Tenants a los que ya se les cobró la cuota del mes. */
433
+ activeTenants: number;
434
+ /** Correos cobrados en el mes (sin los devueltos). */
435
+ monthEmails: number;
436
+ /** Lo gastado en el mes: correos + cuotas − devoluciones. */
437
+ monthSpentCents: number;
438
+ }
439
+ /** `GET /balance/movements?cursor=&limit=&kind=topup,adjust` — del más nuevo al más viejo. */
440
+ export interface BalanceMovementsQuery {
441
+ cursor?: string;
442
+ /** 1–200, default 50. */
443
+ limit?: number;
444
+ kind?: BalanceMovementKind[];
445
+ }
446
+ export interface BalanceMovementsResponse {
447
+ movements: BalanceMovement[];
448
+ nextCursor: string | null;
449
+ }
450
+ /**
451
+ * `POST /topup` — liga de pago único de MercadoPago. El saldo sube cuando MercadoPago confirma
452
+ * el pago (webhook), no al crear la liga. `returnUrl` (https) es a dónde regresa el navegador.
453
+ */
454
+ export interface TopupRequest {
455
+ /** Pesos MXN enteros, 500 a 100,000. */
456
+ amountMxn: number;
457
+ returnUrl?: string;
458
+ }
459
+ export interface TopupResponse {
460
+ topupId: string;
461
+ amountCents: number;
462
+ /** Abre esto en el navegador de quien paga. */
463
+ initPoint: string;
464
+ }
465
+ /**
466
+ * `GET /api/partners/:partnerId/balance` (dueño o MailMask): el saldo, sus últimos 20 movimientos,
467
+ * el partner (con `tenants` y `ownerEmail`) y si quien mira es de MailMask (ve abonar y el cobro).
468
+ */
469
+ export type PartnerBalanceDetail = BalanceResponse & {
470
+ movements: BalanceMovement[];
471
+ partner: PartnerSummary;
472
+ staff: boolean;
473
+ };
474
+ /** `POST /api/partners/:partnerId/balance/adjust` — sólo MailMask (admin): abono o cargo manual. */
475
+ export interface AdjustBalanceRequest {
476
+ /** Centavos MXN, distinto de 0 (negativo = cargo). */
477
+ amountCents: number;
478
+ note: string;
479
+ /** Repetir con la misma llave no vuelve a abonar. */
480
+ idempotencyKey?: string;
481
+ }
482
+ /** `PUT /api/partners/:partnerId/prepaid` — sólo MailMask (admin): prende o apaga el cobro por adelantado. */
483
+ export interface SetPrepaidEnforcedRequest {
484
+ enforced: boolean;
485
+ }
371
486
  /** `GET /webhook` */
372
487
  export interface WebhookConfig {
373
488
  url: string | null;
@@ -401,7 +516,7 @@ export interface RotateWebhookSecretResponse {
401
516
  export interface TestWebhookResponse {
402
517
  deliveryId: string;
403
518
  }
404
- export declare const PARTNER_WEBHOOK_EVENTS: readonly ["email.sent", "email.delivered", "email.bounced", "email.complained", "email.received", "contact.unsubscribed", "tenant.paused", "tenant.resumed", "domain.verified"];
519
+ export declare const PARTNER_WEBHOOK_EVENTS: readonly ["email.sent", "email.delivered", "email.bounced", "email.complained", "email.received", "contact.unsubscribed", "tenant.paused", "tenant.resumed", "domain.verified", "balance.low"];
405
520
  export type PartnerWebhookEvent = typeof PARTNER_WEBHOOK_EVENTS[number];
406
521
  /** Headers de cada entrega al webhook del partner (los mismos que los webhooks de dominio). */
407
522
  export declare const WEBHOOK_HEADERS: {
@@ -479,9 +594,15 @@ export interface PartnerEventData {
479
594
  domain: string;
480
595
  mailFromVerified: boolean;
481
596
  };
597
+ /** El saldo prepagado bajó del 20 % de la última recarga. `externalId` va null. */
598
+ "balance.low": {
599
+ balanceCents: number;
600
+ thresholdCents: number;
601
+ lastTopupCents: number;
602
+ };
482
603
  ping: Record<string, never>;
483
604
  }
484
- /** Sobre de cada evento. `externalId` es null sólo en `ping`. */
605
+ /** Sobre de cada evento. `externalId` es null en `ping` y `balance.low`. */
485
606
  export interface PartnerWebhookPayload<E extends keyof PartnerEventData = keyof PartnerEventData> {
486
607
  /** Id de la entrega (igual que `X-MailMask-Delivery`); úsalo para descartar duplicados. */
487
608
  id: string;
@@ -498,7 +619,7 @@ export interface PartnerWebhookPayload<E extends keyof PartnerEventData = keyof
498
619
  export type ReputationPolicy = "standard" | "strict";
499
620
  /** Primer mes del partner: sus tenants van en Strict salvo que MailMask fije otra política. */
500
621
  export declare const STRICT_FIRST_DAYS = 30;
501
- /** `GET /api/partners` — los partners de la cuenta. */
622
+ /** `GET /api/partners` — los partners de la cuenta. MailMask (admin) con `?all=1`: todos, con `tenants`. */
502
623
  export interface PartnerSummary {
503
624
  id: string;
504
625
  name: string;
@@ -515,6 +636,16 @@ export interface PartnerSummary {
515
636
  effectiveReputationPolicy: ReputationPolicy;
516
637
  /** Zona de respaldo (`marketing.denik.me`); null = sin zona. Ver `FallbackZone`. */
517
638
  fallbackZone: string | null;
639
+ /** Saldo prepagado (centavos MXN) y si ya frena envíos. Ver `BalanceResponse`. */
640
+ balanceCents: number;
641
+ prepaidEnforced: boolean;
642
+ /** Sólo en `GET /api/partners?all=1` (MailMask, admin): tenants totales y activos del mes. */
643
+ tenants?: {
644
+ total: number;
645
+ activeThisMonth: number;
646
+ };
647
+ /** Sólo en `?all=1` (admin): cuenta dueña. */
648
+ ownerEmail?: string;
518
649
  }
519
650
  /** `POST /api/partners` — alta (sólo MailMask, admin). 201 con `PartnerSummary`. */
520
651
  export interface CreatePartnerRequest {
@@ -630,6 +761,18 @@ export declare const PARTNER_ROUTES: {
630
761
  readonly method: "GET";
631
762
  readonly path: "/usage";
632
763
  };
764
+ readonly balance: {
765
+ readonly method: "GET";
766
+ readonly path: "/balance";
767
+ };
768
+ readonly balanceMovements: {
769
+ readonly method: "GET";
770
+ readonly path: "/balance/movements";
771
+ };
772
+ readonly topup: {
773
+ readonly method: "POST";
774
+ readonly path: "/topup";
775
+ };
633
776
  readonly getWebhook: {
634
777
  readonly method: "GET";
635
778
  readonly path: "/webhook";
@@ -676,6 +819,26 @@ export declare const PARTNER_ADMIN_ROUTES: {
676
819
  readonly method: "GET";
677
820
  readonly path: "/api/partners/:partnerId/fallback-zone";
678
821
  };
822
+ /** El saldo del partner (dueño o MailMask). */
823
+ readonly getBalance: {
824
+ readonly method: "GET";
825
+ readonly path: "/api/partners/:partnerId/balance";
826
+ };
827
+ /** Sólo MailMask (admin): abono o cargo manual (`adjust`). */
828
+ readonly adjustBalance: {
829
+ readonly method: "POST";
830
+ readonly path: "/api/partners/:partnerId/balance/adjust";
831
+ };
832
+ /** Sólo MailMask (admin): prende o apaga el cobro por adelantado. */
833
+ readonly setPrepaidEnforced: {
834
+ readonly method: "PUT";
835
+ readonly path: "/api/partners/:partnerId/prepaid";
836
+ };
837
+ /** Recarga con MercadoPago desde la sesión del dueño (misma respuesta que `POST /topup`). */
838
+ readonly topup: {
839
+ readonly method: "POST";
840
+ readonly path: "/api/partners/:partnerId/topup";
841
+ };
679
842
  readonly listCredentials: {
680
843
  readonly method: "GET";
681
844
  readonly path: "/api/partners/:partnerId/credentials";
package/dist/partner.d.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { MailMaskError } from "./client.js";
2
- import type { TemplateVars, PartnerSummary, CreatePartnerRequest, ReputationPolicy, ListCredentialsResponse, CreateCredentialRequest, CreateCredentialResponse, RotateCredentialRequest, RotateCredentialResponse, RevokeCredentialResponse, PartnerErrorBody, PartnerErrorCode, Tenant, PutTenantRequest, ListTenantsQuery, ListTenantsResponse, DeleteTenantResponse, PartnerDomain, SendRequest, SendResponse, BatchRequest, BatchStatus, UsageResponse, WebhookConfig, SetWebhookRequest, SetWebhookResponse, TestWebhookResponse, PartnerWebhookPayload, RotateWebhookSecretRequest, RotateWebhookSecretResponse, MessageStream, SuppressionReason, FallbackZone } from "./partner-contract.js";
2
+ import type { TemplateVars, PartnerSummary, CreatePartnerRequest, ReputationPolicy, ListCredentialsResponse, CreateCredentialRequest, CreateCredentialResponse, RotateCredentialRequest, RotateCredentialResponse, RevokeCredentialResponse, PartnerErrorBody, PartnerErrorCode, Tenant, PutTenantRequest, ListTenantsQuery, ListTenantsResponse, DeleteTenantResponse, PartnerDomain, SendRequest, SendResponse, BatchRequest, BatchStatus, UsageResponse, WebhookConfig, SetWebhookRequest, SetWebhookResponse, TestWebhookResponse, PartnerWebhookPayload, RotateWebhookSecretRequest, RotateWebhookSecretResponse, MessageStream, SuppressionReason, FallbackZone, BalanceResponse, BalanceMovementsQuery, BalanceMovementsResponse, TopupRequest, TopupResponse, PartnerBalanceDetail, AdjustBalanceRequest } from "./partner-contract.js";
3
3
  export * from "./partner-contract.js";
4
4
  /**
5
5
  * Discriminante estable de los errores de partners, para un `switch` sin cadenas de `instanceof`.
6
6
  * `network` y `timeout` llevan `status` 0 (no hubo respuesta); `unknown` es un status que el
7
7
  * contrato no define (p. ej. un 400 de un proxy).
8
8
  */
9
- export type PartnerErrorKind = "auth" | "forbidden" | "reputation_hold" | "not_found" | "conflict" | "validation" | "suppressed" | "paused" | "quota" | "rate_limit" | "server" | "network" | "timeout" | "unknown";
9
+ export type PartnerErrorKind = "auth" | "forbidden" | "reputation_hold" | "not_found" | "conflict" | "validation" | "suppressed" | "paused" | "quota" | "insufficient_balance" | "rate_limit" | "server" | "network" | "timeout" | "unknown";
10
10
  /** Metadatos de la respuesta que no vienen en el cuerpo. */
11
11
  export interface PartnerErrorMeta {
12
12
  requestId?: string;
@@ -100,6 +100,16 @@ export declare class RateLimitError extends QuotaError {
100
100
  name: string;
101
101
  kind: PartnerErrorKind;
102
102
  }
103
+ /**
104
+ * 402 `insufficient_balance`: el saldo prepagado no alcanza (sólo con el cobro por adelantado
105
+ * prendido). No se reintenta: recarga con `partner.topup()`. Trae `balanceCents` y `requiredCents`.
106
+ */
107
+ export declare class InsufficientBalanceError extends PartnerApiError {
108
+ name: string;
109
+ kind: PartnerErrorKind;
110
+ get balanceCents(): number | undefined;
111
+ get requiredCents(): number | undefined;
112
+ }
103
113
  /** No hubo respuesta: DNS, conexión rechazada o cortada. `status` 0. */
104
114
  export declare class NetworkError extends PartnerApiError {
105
115
  name: string;
@@ -218,6 +228,15 @@ export declare class MailMaskPartner {
218
228
  }>;
219
229
  /** Estado de un lote (`batchId` del resultado de `batch`): envíos y, después, entregas, rebotes, quejas y bajas. */
220
230
  batchStatus(externalId: string, batchId: string): Promise<BatchStatus>;
231
+ /** Saldo prepagado: saldo, precios, última recarga y consumo del mes (centavos MXN). */
232
+ balance(): Promise<BalanceResponse>;
233
+ /** Movimientos del saldo, del más nuevo al más viejo; pagina con `nextCursor`. */
234
+ balanceMovements(query?: BalanceMovementsQuery): Promise<BalanceMovementsResponse>;
235
+ /**
236
+ * Liga de MercadoPago para recargar saldo (pago único, $500 a $100,000 MXN). El saldo sube cuando
237
+ * MercadoPago confirma el pago, no al crear la liga. Abre `initPoint` en el navegador de quien paga.
238
+ */
239
+ topup(input: TopupRequest): Promise<TopupResponse>;
221
240
  /** Uso del mes (`YYYY-MM`, default el mes en curso UTC): tenants activos y envíos. */
222
241
  usage(opts?: {
223
242
  month?: string;
@@ -361,6 +380,14 @@ export declare class MailMaskPartnerAdmin {
361
380
  setFallbackZone(partnerId: string, zone: string): Promise<FallbackZone>;
362
381
  /** La zona de respaldo y si el DNS público ya ve la delegación. */
363
382
  getFallbackZone(partnerId: string): Promise<FallbackZone>;
383
+ /** Saldo del partner con sus últimos 20 movimientos (dueño o MailMask). */
384
+ getBalance(partnerId: string): Promise<PartnerBalanceDetail>;
385
+ /** Sólo MailMask (admin): abono (o cargo, negativo) manual. Con la misma `idempotencyKey` no se repite. */
386
+ adjustBalance(partnerId: string, input: AdjustBalanceRequest): Promise<PartnerBalanceDetail>;
387
+ /** Sólo MailMask (admin): prende o apaga el cobro por adelantado del partner. */
388
+ setPrepaidEnforced(partnerId: string, enforced: boolean): Promise<PartnerSummary>;
389
+ /** Liga de MercadoPago para recargar, con la llave del dueño. */
390
+ topup(partnerId: string, input: TopupRequest): Promise<TopupResponse>;
364
391
  listCredentials(partnerId: string): Promise<ListCredentialsResponse>;
365
392
  /** El secreto `mms_…` viene UNA vez en la respuesta. */
366
393
  createCredential(partnerId: string, input?: CreateCredentialRequest): Promise<CreateCredentialResponse>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@easybits.cloud/mailmask",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Official SDK for the MailMask email alias/forwarding API",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",