@easybits.cloud/mailmask 0.6.1 → 0.8.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 +148 -4
- package/dist/index.js +134 -4
- package/dist/partner-contract.d.ts +227 -5
- package/dist/partner.d.ts +38 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -21,11 +21,15 @@ 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,
|
|
28
|
+
DailyLimitError: () => DailyLimitError,
|
|
26
29
|
EXTERNAL_ID_RE: () => EXTERNAL_ID_RE,
|
|
27
30
|
FALLBACK_LABEL_RE: () => FALLBACK_LABEL_RE,
|
|
28
31
|
ForbiddenError: () => ForbiddenError,
|
|
32
|
+
InsufficientBalanceError: () => InsufficientBalanceError,
|
|
29
33
|
MAX_BATCH_RECIPIENTS: () => MAX_BATCH_RECIPIENTS,
|
|
30
34
|
MAX_TAGS: () => MAX_TAGS,
|
|
31
35
|
MAX_TEMPLATE_VARS: () => MAX_TEMPLATE_VARS,
|
|
@@ -42,9 +46,14 @@ __export(index_exports, {
|
|
|
42
46
|
PARTNER_API_PREFIX: () => PARTNER_API_PREFIX,
|
|
43
47
|
PARTNER_ERROR_STATUS: () => PARTNER_ERROR_STATUS,
|
|
44
48
|
PARTNER_HEADERS: () => PARTNER_HEADERS,
|
|
49
|
+
PARTNER_PRICE_PER_EMAIL_CENTS: () => PARTNER_PRICE_PER_EMAIL_CENTS,
|
|
45
50
|
PARTNER_ROUTES: () => PARTNER_ROUTES,
|
|
46
51
|
PARTNER_SIGNATURE_TOLERANCE_S: () => PARTNER_SIGNATURE_TOLERANCE_S,
|
|
52
|
+
PARTNER_TENANT_MONTH_CENTS: () => PARTNER_TENANT_MONTH_CENTS,
|
|
53
|
+
PARTNER_TOPUP_MAX_CENTS: () => PARTNER_TOPUP_MAX_CENTS,
|
|
54
|
+
PARTNER_TOPUP_MIN_CENTS: () => PARTNER_TOPUP_MIN_CENTS,
|
|
47
55
|
PARTNER_WEBHOOK_EVENTS: () => PARTNER_WEBHOOK_EVENTS,
|
|
56
|
+
PARTNER_WELCOME_CENTS: () => PARTNER_WELCOME_CENTS,
|
|
48
57
|
PartnerApiError: () => PartnerApiError,
|
|
49
58
|
PausedError: () => PausedError,
|
|
50
59
|
QuotaError: () => QuotaError,
|
|
@@ -55,12 +64,16 @@ __export(index_exports, {
|
|
|
55
64
|
STRICT_FIRST_DAYS: () => STRICT_FIRST_DAYS,
|
|
56
65
|
TAG_RE: () => TAG_RE,
|
|
57
66
|
TEMPLATE_VAR_NAME_RE: () => TEMPLATE_VAR_NAME_RE,
|
|
67
|
+
TENANT_DAILY_LIMIT_MAX: () => TENANT_DAILY_LIMIT_MAX,
|
|
68
|
+
TENANT_DAILY_LIMIT_START: () => TENANT_DAILY_LIMIT_START,
|
|
69
|
+
TENANT_WARMUP_STEP_DAYS: () => TENANT_WARMUP_STEP_DAYS,
|
|
58
70
|
TimeoutError: () => TimeoutError,
|
|
59
71
|
UNSUBSCRIBE_URL_VAR: () => UNSUBSCRIBE_URL_VAR,
|
|
60
72
|
ValidationError: () => ValidationError,
|
|
61
73
|
WEBHOOK_HEADERS: () => WEBHOOK_HEADERS,
|
|
62
74
|
WEBHOOK_TOLERANCE_MS: () => WEBHOOK_TOLERANCE_MS,
|
|
63
75
|
createWebhookHandler: () => createWebhookHandler,
|
|
76
|
+
estimateSendDays: () => estimateSendDays,
|
|
64
77
|
isValidExternalId: () => isValidExternalId,
|
|
65
78
|
isValidFallbackLabel: () => isValidFallbackLabel,
|
|
66
79
|
partnerSignatureBase: () => partnerSignatureBase,
|
|
@@ -73,6 +86,7 @@ __export(index_exports, {
|
|
|
73
86
|
templateVarsProblem: () => templateVarsProblem,
|
|
74
87
|
verifyWebhook: () => verifyWebhook,
|
|
75
88
|
verifyWebhookSignature: () => verifyWebhookSignature,
|
|
89
|
+
warmupLimit: () => warmupLimit,
|
|
76
90
|
webhookSignatureBase: () => webhookSignatureBase
|
|
77
91
|
});
|
|
78
92
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -716,7 +730,17 @@ var PARTNER_ERROR_STATUS = {
|
|
|
716
730
|
recipient_suppressed: 422,
|
|
717
731
|
/** El tenant está en pausa (por MailMask o por el partner). No se manda nada hasta `resume`. */
|
|
718
732
|
tenant_paused: 423,
|
|
719
|
-
/**
|
|
733
|
+
/**
|
|
734
|
+
* Saldo prepagado insuficiente (con `balanceCents` y `requiredCents`). No se reintenta: recarga
|
|
735
|
+
* con `POST /topup`.
|
|
736
|
+
*/
|
|
737
|
+
insufficient_balance: 402,
|
|
738
|
+
/**
|
|
739
|
+
* El tenant llegó a su límite del día (con `limit`, `sentToday` y `resetsAt`, la medianoche UTC).
|
|
740
|
+
* Sólo en envíos sueltos: un lote no falla, espera al día siguiente (`batch.throttled`).
|
|
741
|
+
*/
|
|
742
|
+
daily_limit: 429,
|
|
743
|
+
/** Ya no sale desde la 0.8 (no hay tope mensual); se deja para clientes viejos. */
|
|
720
744
|
quota_exceeded: 429,
|
|
721
745
|
/** Demasiadas peticiones; reintenta tras `Retry-After`. */
|
|
722
746
|
rate_limited: 429,
|
|
@@ -730,6 +754,30 @@ function isValidExternalId(id) {
|
|
|
730
754
|
var MAX_BATCH_RECIPIENTS = 1e4;
|
|
731
755
|
var MAX_TAGS = 10;
|
|
732
756
|
var TAG_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
757
|
+
var TENANT_DAILY_LIMIT_START = 2e3;
|
|
758
|
+
var TENANT_DAILY_LIMIT_MAX = 5e4;
|
|
759
|
+
var TENANT_WARMUP_STEP_DAYS = 7;
|
|
760
|
+
function warmupLimit(step) {
|
|
761
|
+
return Math.min(TENANT_DAILY_LIMIT_MAX, TENANT_DAILY_LIMIT_START * 2 ** Math.max(0, step));
|
|
762
|
+
}
|
|
763
|
+
function estimateSendDays(s, recipients) {
|
|
764
|
+
let left = recipients - Math.max(0, s.dailyLimit - s.sentToday);
|
|
765
|
+
if (left <= 0) return 1;
|
|
766
|
+
let days = 1;
|
|
767
|
+
let limit = s.dailyLimit;
|
|
768
|
+
let nextAt = s.nextStepAt ? Date.parse(s.nextStepAt) : null;
|
|
769
|
+
let day = Date.parse(s.resetsAt);
|
|
770
|
+
while (left > 0 && days < 3650) {
|
|
771
|
+
while (nextAt !== null && nextAt <= day && limit < TENANT_DAILY_LIMIT_MAX && !s.fixed) {
|
|
772
|
+
limit = Math.min(TENANT_DAILY_LIMIT_MAX, limit * 2);
|
|
773
|
+
nextAt += TENANT_WARMUP_STEP_DAYS * 864e5;
|
|
774
|
+
}
|
|
775
|
+
left -= limit;
|
|
776
|
+
days++;
|
|
777
|
+
day += 864e5;
|
|
778
|
+
}
|
|
779
|
+
return days;
|
|
780
|
+
}
|
|
733
781
|
var DOMAIN_CLAIM_TTL_DAYS = 7;
|
|
734
782
|
var FALLBACK_LABEL_RE = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
735
783
|
var RESERVED_FALLBACK_LABELS = [
|
|
@@ -795,6 +843,13 @@ function personalizePartnerMessage(input) {
|
|
|
795
843
|
...text !== void 0 ? { text: renderTemplate(text, vars) } : {}
|
|
796
844
|
};
|
|
797
845
|
}
|
|
846
|
+
var PARTNER_PRICE_PER_EMAIL_CENTS = 6;
|
|
847
|
+
var PARTNER_TENANT_MONTH_CENTS = 4900;
|
|
848
|
+
var PARTNER_WELCOME_CENTS = 5e4;
|
|
849
|
+
var PARTNER_TOPUP_MIN_CENTS = 5e4;
|
|
850
|
+
var PARTNER_TOPUP_MAX_CENTS = 1e7;
|
|
851
|
+
var BALANCE_LOW_FRACTION = 0.2;
|
|
852
|
+
var BALANCE_MOVEMENT_KINDS = ["topup", "send", "tenant_fee", "refund", "adjust"];
|
|
798
853
|
var PARTNER_WEBHOOK_EVENTS = [
|
|
799
854
|
"email.sent",
|
|
800
855
|
"email.delivered",
|
|
@@ -804,7 +859,9 @@ var PARTNER_WEBHOOK_EVENTS = [
|
|
|
804
859
|
"contact.unsubscribed",
|
|
805
860
|
"tenant.paused",
|
|
806
861
|
"tenant.resumed",
|
|
807
|
-
"domain.verified"
|
|
862
|
+
"domain.verified",
|
|
863
|
+
"balance.low",
|
|
864
|
+
"batch.throttled"
|
|
808
865
|
];
|
|
809
866
|
var WEBHOOK_HEADERS = {
|
|
810
867
|
event: "X-MailMask-Event",
|
|
@@ -836,6 +893,9 @@ var PARTNER_ROUTES = {
|
|
|
836
893
|
batch: { method: "POST", path: "/tenants/:externalId/batch" },
|
|
837
894
|
getBatch: { method: "GET", path: "/tenants/:externalId/batch/:batchId" },
|
|
838
895
|
usage: { method: "GET", path: "/usage" },
|
|
896
|
+
balance: { method: "GET", path: "/balance" },
|
|
897
|
+
balanceMovements: { method: "GET", path: "/balance/movements" },
|
|
898
|
+
topup: { method: "POST", path: "/topup" },
|
|
839
899
|
getWebhook: { method: "GET", path: "/webhook" },
|
|
840
900
|
setWebhook: { method: "PUT", path: "/webhook" },
|
|
841
901
|
testWebhook: { method: "POST", path: "/webhook/test" },
|
|
@@ -851,6 +911,14 @@ var PARTNER_ADMIN_ROUTES = {
|
|
|
851
911
|
setFallbackZone: { method: "PUT", path: "/api/partners/:partnerId/fallback-zone" },
|
|
852
912
|
/** La zona de respaldo y si la delegación ya se ve en el DNS público. */
|
|
853
913
|
getFallbackZone: { method: "GET", path: "/api/partners/:partnerId/fallback-zone" },
|
|
914
|
+
/** El saldo del partner (dueño o MailMask). */
|
|
915
|
+
getBalance: { method: "GET", path: "/api/partners/:partnerId/balance" },
|
|
916
|
+
/** Sólo MailMask (admin): abono o cargo manual (`adjust`). */
|
|
917
|
+
adjustBalance: { method: "POST", path: "/api/partners/:partnerId/balance/adjust" },
|
|
918
|
+
/** Recarga con MercadoPago desde la sesión del dueño (misma respuesta que `POST /topup`). */
|
|
919
|
+
topup: { method: "POST", path: "/api/partners/:partnerId/topup" },
|
|
920
|
+
/** Sólo MailMask: fija (o quita, con `null`) el límite diario de un tenant. Devuelve el `Tenant`. */
|
|
921
|
+
setTenantDailyLimit: { method: "PUT", path: "/api/partners/:partnerId/tenants/:externalId/daily-limit" },
|
|
854
922
|
listCredentials: { method: "GET", path: "/api/partners/:partnerId/credentials" },
|
|
855
923
|
createCredential: { method: "POST", path: "/api/partners/:partnerId/credentials" },
|
|
856
924
|
rotateCredential: { method: "POST", path: "/api/partners/:partnerId/credentials/:keyId/rotate" },
|
|
@@ -864,8 +932,8 @@ var PartnerApiError = class extends MailMaskError {
|
|
|
864
932
|
code: body.code,
|
|
865
933
|
requestId: meta.requestId,
|
|
866
934
|
cause: meta.cause,
|
|
867
|
-
// `quota_exceeded`
|
|
868
|
-
retryable: status === 0 || status >= 500 || status === 429 && body.code !== "quota_exceeded"
|
|
935
|
+
// `daily_limit` (y el viejo `quota_exceeded`) son 429 que no se arreglan reintentando: hasta mañana.
|
|
936
|
+
retryable: status === 0 || status >= 500 || status === 429 && body.code !== "quota_exceeded" && body.code !== "daily_limit"
|
|
869
937
|
});
|
|
870
938
|
this.kind = "unknown";
|
|
871
939
|
this.name = "PartnerApiError";
|
|
@@ -968,6 +1036,29 @@ var RateLimitError = class extends QuotaError {
|
|
|
968
1036
|
this.kind = "rate_limit";
|
|
969
1037
|
}
|
|
970
1038
|
};
|
|
1039
|
+
var DailyLimitError = class extends QuotaError {
|
|
1040
|
+
constructor() {
|
|
1041
|
+
super(...arguments);
|
|
1042
|
+
this.name = "DailyLimitError";
|
|
1043
|
+
this.kind = "daily_limit";
|
|
1044
|
+
}
|
|
1045
|
+
get sentToday() {
|
|
1046
|
+
return this.body.sentToday;
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
var InsufficientBalanceError = class extends PartnerApiError {
|
|
1050
|
+
constructor() {
|
|
1051
|
+
super(...arguments);
|
|
1052
|
+
this.name = "InsufficientBalanceError";
|
|
1053
|
+
this.kind = "insufficient_balance";
|
|
1054
|
+
}
|
|
1055
|
+
get balanceCents() {
|
|
1056
|
+
return this.body.balanceCents;
|
|
1057
|
+
}
|
|
1058
|
+
get requiredCents() {
|
|
1059
|
+
return this.body.requiredCents;
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
971
1062
|
var NetworkError = class extends PartnerApiError {
|
|
972
1063
|
constructor(message, cause) {
|
|
973
1064
|
super(0, message, {}, { cause });
|
|
@@ -986,10 +1077,13 @@ function errorFor(status, body, fallback, meta = {}) {
|
|
|
986
1077
|
const msg = body.error || fallback;
|
|
987
1078
|
if (body.code === "reputation_hold") return new ReputationHoldError(status, msg, body, meta);
|
|
988
1079
|
if (body.code === "recipient_suppressed") return new RecipientSuppressedError(status, msg, body, meta);
|
|
1080
|
+
if (status === 429 && body.code === "daily_limit") return new DailyLimitError(status, msg, body, meta);
|
|
989
1081
|
if (status === 429 && body.code !== "quota_exceeded") return new RateLimitError(status, msg, body, meta);
|
|
990
1082
|
switch (status) {
|
|
991
1083
|
case 401:
|
|
992
1084
|
return new AuthError(status, msg, body, meta);
|
|
1085
|
+
case 402:
|
|
1086
|
+
return new InsufficientBalanceError(status, msg, body, meta);
|
|
993
1087
|
case 403:
|
|
994
1088
|
return new ForbiddenError(status, msg, body, meta);
|
|
995
1089
|
case 404:
|
|
@@ -1190,6 +1284,26 @@ var MailMaskPartner = class {
|
|
|
1190
1284
|
async batchStatus(externalId, batchId) {
|
|
1191
1285
|
return (await this.request("GET", routePath(PARTNER_ROUTES.getBatch.path, { externalId, batchId }))).data;
|
|
1192
1286
|
}
|
|
1287
|
+
/** Saldo prepagado: saldo, precios, última recarga y consumo del mes (centavos MXN). */
|
|
1288
|
+
async balance() {
|
|
1289
|
+
return (await this.request("GET", PARTNER_ROUTES.balance.path)).data;
|
|
1290
|
+
}
|
|
1291
|
+
/** Movimientos del saldo, del más nuevo al más viejo; pagina con `nextCursor`. */
|
|
1292
|
+
async balanceMovements(query = {}) {
|
|
1293
|
+
const q = new URLSearchParams();
|
|
1294
|
+
if (query.cursor) q.set("cursor", query.cursor);
|
|
1295
|
+
if (query.limit !== void 0) q.set("limit", String(query.limit));
|
|
1296
|
+
if (query.kind?.length) q.set("kind", query.kind.join(","));
|
|
1297
|
+
const qs = q.toString();
|
|
1298
|
+
return (await this.request("GET", `${PARTNER_ROUTES.balanceMovements.path}${qs ? `?${qs}` : ""}`)).data;
|
|
1299
|
+
}
|
|
1300
|
+
/**
|
|
1301
|
+
* Liga de MercadoPago para recargar saldo (pago único, $500 a $100,000 MXN). El saldo sube cuando
|
|
1302
|
+
* MercadoPago confirma el pago, no al crear la liga. Abre `initPoint` en el navegador de quien paga.
|
|
1303
|
+
*/
|
|
1304
|
+
async topup(input) {
|
|
1305
|
+
return (await this.request("POST", PARTNER_ROUTES.topup.path, input)).data;
|
|
1306
|
+
}
|
|
1193
1307
|
/** Uso del mes (`YYYY-MM`, default el mes en curso UTC): tenants activos y envíos. */
|
|
1194
1308
|
async usage(opts = {}) {
|
|
1195
1309
|
const q = opts.month ? `?month=${encodeURIComponent(opts.month)}` : "";
|
|
@@ -1394,6 +1508,10 @@ var MailMaskPartnerAdmin = class {
|
|
|
1394
1508
|
resumeTenant(partnerId, externalId) {
|
|
1395
1509
|
return this.req("resumeTenant", { partnerId, externalId });
|
|
1396
1510
|
}
|
|
1511
|
+
/** Sólo MailMask: tope diario fijo del tenant, o `null` para volver al calentamiento. */
|
|
1512
|
+
setTenantDailyLimit(partnerId, externalId, limit) {
|
|
1513
|
+
return this.req("setTenantDailyLimit", { partnerId, externalId }, { limit });
|
|
1514
|
+
}
|
|
1397
1515
|
/**
|
|
1398
1516
|
* Sólo MailMask (admin): crea o adopta la hosted zone de la zona de respaldo (`marketing.denik.me`)
|
|
1399
1517
|
* y devuelve los NS que el partner tiene que delegar. Idempotente con la misma zona.
|
|
@@ -1405,6 +1523,18 @@ var MailMaskPartnerAdmin = class {
|
|
|
1405
1523
|
getFallbackZone(partnerId) {
|
|
1406
1524
|
return this.req("getFallbackZone", { partnerId });
|
|
1407
1525
|
}
|
|
1526
|
+
/** Saldo del partner con sus últimos 20 movimientos (dueño o MailMask). */
|
|
1527
|
+
getBalance(partnerId) {
|
|
1528
|
+
return this.req("getBalance", { partnerId });
|
|
1529
|
+
}
|
|
1530
|
+
/** Sólo MailMask (admin): abono (o cargo, negativo) manual. Con la misma `idempotencyKey` no se repite. */
|
|
1531
|
+
adjustBalance(partnerId, input) {
|
|
1532
|
+
return this.req("adjustBalance", { partnerId }, input);
|
|
1533
|
+
}
|
|
1534
|
+
/** Liga de MercadoPago para recargar, con la llave del dueño. */
|
|
1535
|
+
topup(partnerId, input) {
|
|
1536
|
+
return this.req("topup", { partnerId }, input);
|
|
1537
|
+
}
|
|
1408
1538
|
listCredentials(partnerId) {
|
|
1409
1539
|
return this.req("listCredentials", { partnerId });
|
|
1410
1540
|
}
|
|
@@ -1424,11 +1554,15 @@ var MailMaskPartnerAdmin = class {
|
|
|
1424
1554
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1425
1555
|
0 && (module.exports = {
|
|
1426
1556
|
AuthError,
|
|
1557
|
+
BALANCE_LOW_FRACTION,
|
|
1558
|
+
BALANCE_MOVEMENT_KINDS,
|
|
1427
1559
|
ConflictError,
|
|
1428
1560
|
DOMAIN_CLAIM_TTL_DAYS,
|
|
1561
|
+
DailyLimitError,
|
|
1429
1562
|
EXTERNAL_ID_RE,
|
|
1430
1563
|
FALLBACK_LABEL_RE,
|
|
1431
1564
|
ForbiddenError,
|
|
1565
|
+
InsufficientBalanceError,
|
|
1432
1566
|
MAX_BATCH_RECIPIENTS,
|
|
1433
1567
|
MAX_TAGS,
|
|
1434
1568
|
MAX_TEMPLATE_VARS,
|
|
@@ -1445,9 +1579,14 @@ var MailMaskPartnerAdmin = class {
|
|
|
1445
1579
|
PARTNER_API_PREFIX,
|
|
1446
1580
|
PARTNER_ERROR_STATUS,
|
|
1447
1581
|
PARTNER_HEADERS,
|
|
1582
|
+
PARTNER_PRICE_PER_EMAIL_CENTS,
|
|
1448
1583
|
PARTNER_ROUTES,
|
|
1449
1584
|
PARTNER_SIGNATURE_TOLERANCE_S,
|
|
1585
|
+
PARTNER_TENANT_MONTH_CENTS,
|
|
1586
|
+
PARTNER_TOPUP_MAX_CENTS,
|
|
1587
|
+
PARTNER_TOPUP_MIN_CENTS,
|
|
1450
1588
|
PARTNER_WEBHOOK_EVENTS,
|
|
1589
|
+
PARTNER_WELCOME_CENTS,
|
|
1451
1590
|
PartnerApiError,
|
|
1452
1591
|
PausedError,
|
|
1453
1592
|
QuotaError,
|
|
@@ -1458,12 +1597,16 @@ var MailMaskPartnerAdmin = class {
|
|
|
1458
1597
|
STRICT_FIRST_DAYS,
|
|
1459
1598
|
TAG_RE,
|
|
1460
1599
|
TEMPLATE_VAR_NAME_RE,
|
|
1600
|
+
TENANT_DAILY_LIMIT_MAX,
|
|
1601
|
+
TENANT_DAILY_LIMIT_START,
|
|
1602
|
+
TENANT_WARMUP_STEP_DAYS,
|
|
1461
1603
|
TimeoutError,
|
|
1462
1604
|
UNSUBSCRIBE_URL_VAR,
|
|
1463
1605
|
ValidationError,
|
|
1464
1606
|
WEBHOOK_HEADERS,
|
|
1465
1607
|
WEBHOOK_TOLERANCE_MS,
|
|
1466
1608
|
createWebhookHandler,
|
|
1609
|
+
estimateSendDays,
|
|
1467
1610
|
isValidExternalId,
|
|
1468
1611
|
isValidFallbackLabel,
|
|
1469
1612
|
partnerSignatureBase,
|
|
@@ -1476,5 +1619,6 @@ var MailMaskPartnerAdmin = class {
|
|
|
1476
1619
|
templateVarsProblem,
|
|
1477
1620
|
verifyWebhook,
|
|
1478
1621
|
verifyWebhookSignature,
|
|
1622
|
+
warmupLimit,
|
|
1479
1623
|
webhookSignatureBase
|
|
1480
1624
|
});
|
package/dist/index.js
CHANGED
|
@@ -637,7 +637,17 @@ 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
|
-
/**
|
|
640
|
+
/**
|
|
641
|
+
* Saldo prepagado insuficiente (con `balanceCents` y `requiredCents`). No se reintenta: recarga
|
|
642
|
+
* con `POST /topup`.
|
|
643
|
+
*/
|
|
644
|
+
insufficient_balance: 402,
|
|
645
|
+
/**
|
|
646
|
+
* El tenant llegó a su límite del día (con `limit`, `sentToday` y `resetsAt`, la medianoche UTC).
|
|
647
|
+
* Sólo en envíos sueltos: un lote no falla, espera al día siguiente (`batch.throttled`).
|
|
648
|
+
*/
|
|
649
|
+
daily_limit: 429,
|
|
650
|
+
/** Ya no sale desde la 0.8 (no hay tope mensual); se deja para clientes viejos. */
|
|
641
651
|
quota_exceeded: 429,
|
|
642
652
|
/** Demasiadas peticiones; reintenta tras `Retry-After`. */
|
|
643
653
|
rate_limited: 429,
|
|
@@ -651,6 +661,30 @@ function isValidExternalId(id) {
|
|
|
651
661
|
var MAX_BATCH_RECIPIENTS = 1e4;
|
|
652
662
|
var MAX_TAGS = 10;
|
|
653
663
|
var TAG_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
664
|
+
var TENANT_DAILY_LIMIT_START = 2e3;
|
|
665
|
+
var TENANT_DAILY_LIMIT_MAX = 5e4;
|
|
666
|
+
var TENANT_WARMUP_STEP_DAYS = 7;
|
|
667
|
+
function warmupLimit(step) {
|
|
668
|
+
return Math.min(TENANT_DAILY_LIMIT_MAX, TENANT_DAILY_LIMIT_START * 2 ** Math.max(0, step));
|
|
669
|
+
}
|
|
670
|
+
function estimateSendDays(s, recipients) {
|
|
671
|
+
let left = recipients - Math.max(0, s.dailyLimit - s.sentToday);
|
|
672
|
+
if (left <= 0) return 1;
|
|
673
|
+
let days = 1;
|
|
674
|
+
let limit = s.dailyLimit;
|
|
675
|
+
let nextAt = s.nextStepAt ? Date.parse(s.nextStepAt) : null;
|
|
676
|
+
let day = Date.parse(s.resetsAt);
|
|
677
|
+
while (left > 0 && days < 3650) {
|
|
678
|
+
while (nextAt !== null && nextAt <= day && limit < TENANT_DAILY_LIMIT_MAX && !s.fixed) {
|
|
679
|
+
limit = Math.min(TENANT_DAILY_LIMIT_MAX, limit * 2);
|
|
680
|
+
nextAt += TENANT_WARMUP_STEP_DAYS * 864e5;
|
|
681
|
+
}
|
|
682
|
+
left -= limit;
|
|
683
|
+
days++;
|
|
684
|
+
day += 864e5;
|
|
685
|
+
}
|
|
686
|
+
return days;
|
|
687
|
+
}
|
|
654
688
|
var DOMAIN_CLAIM_TTL_DAYS = 7;
|
|
655
689
|
var FALLBACK_LABEL_RE = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
656
690
|
var RESERVED_FALLBACK_LABELS = [
|
|
@@ -716,6 +750,13 @@ function personalizePartnerMessage(input) {
|
|
|
716
750
|
...text !== void 0 ? { text: renderTemplate(text, vars) } : {}
|
|
717
751
|
};
|
|
718
752
|
}
|
|
753
|
+
var PARTNER_PRICE_PER_EMAIL_CENTS = 6;
|
|
754
|
+
var PARTNER_TENANT_MONTH_CENTS = 4900;
|
|
755
|
+
var PARTNER_WELCOME_CENTS = 5e4;
|
|
756
|
+
var PARTNER_TOPUP_MIN_CENTS = 5e4;
|
|
757
|
+
var PARTNER_TOPUP_MAX_CENTS = 1e7;
|
|
758
|
+
var BALANCE_LOW_FRACTION = 0.2;
|
|
759
|
+
var BALANCE_MOVEMENT_KINDS = ["topup", "send", "tenant_fee", "refund", "adjust"];
|
|
719
760
|
var PARTNER_WEBHOOK_EVENTS = [
|
|
720
761
|
"email.sent",
|
|
721
762
|
"email.delivered",
|
|
@@ -725,7 +766,9 @@ var PARTNER_WEBHOOK_EVENTS = [
|
|
|
725
766
|
"contact.unsubscribed",
|
|
726
767
|
"tenant.paused",
|
|
727
768
|
"tenant.resumed",
|
|
728
|
-
"domain.verified"
|
|
769
|
+
"domain.verified",
|
|
770
|
+
"balance.low",
|
|
771
|
+
"batch.throttled"
|
|
729
772
|
];
|
|
730
773
|
var WEBHOOK_HEADERS = {
|
|
731
774
|
event: "X-MailMask-Event",
|
|
@@ -757,6 +800,9 @@ var PARTNER_ROUTES = {
|
|
|
757
800
|
batch: { method: "POST", path: "/tenants/:externalId/batch" },
|
|
758
801
|
getBatch: { method: "GET", path: "/tenants/:externalId/batch/:batchId" },
|
|
759
802
|
usage: { method: "GET", path: "/usage" },
|
|
803
|
+
balance: { method: "GET", path: "/balance" },
|
|
804
|
+
balanceMovements: { method: "GET", path: "/balance/movements" },
|
|
805
|
+
topup: { method: "POST", path: "/topup" },
|
|
760
806
|
getWebhook: { method: "GET", path: "/webhook" },
|
|
761
807
|
setWebhook: { method: "PUT", path: "/webhook" },
|
|
762
808
|
testWebhook: { method: "POST", path: "/webhook/test" },
|
|
@@ -772,6 +818,14 @@ var PARTNER_ADMIN_ROUTES = {
|
|
|
772
818
|
setFallbackZone: { method: "PUT", path: "/api/partners/:partnerId/fallback-zone" },
|
|
773
819
|
/** La zona de respaldo y si la delegación ya se ve en el DNS público. */
|
|
774
820
|
getFallbackZone: { method: "GET", path: "/api/partners/:partnerId/fallback-zone" },
|
|
821
|
+
/** El saldo del partner (dueño o MailMask). */
|
|
822
|
+
getBalance: { method: "GET", path: "/api/partners/:partnerId/balance" },
|
|
823
|
+
/** Sólo MailMask (admin): abono o cargo manual (`adjust`). */
|
|
824
|
+
adjustBalance: { method: "POST", path: "/api/partners/:partnerId/balance/adjust" },
|
|
825
|
+
/** Recarga con MercadoPago desde la sesión del dueño (misma respuesta que `POST /topup`). */
|
|
826
|
+
topup: { method: "POST", path: "/api/partners/:partnerId/topup" },
|
|
827
|
+
/** Sólo MailMask: fija (o quita, con `null`) el límite diario de un tenant. Devuelve el `Tenant`. */
|
|
828
|
+
setTenantDailyLimit: { method: "PUT", path: "/api/partners/:partnerId/tenants/:externalId/daily-limit" },
|
|
775
829
|
listCredentials: { method: "GET", path: "/api/partners/:partnerId/credentials" },
|
|
776
830
|
createCredential: { method: "POST", path: "/api/partners/:partnerId/credentials" },
|
|
777
831
|
rotateCredential: { method: "POST", path: "/api/partners/:partnerId/credentials/:keyId/rotate" },
|
|
@@ -785,8 +839,8 @@ var PartnerApiError = class extends MailMaskError {
|
|
|
785
839
|
code: body.code,
|
|
786
840
|
requestId: meta.requestId,
|
|
787
841
|
cause: meta.cause,
|
|
788
|
-
// `quota_exceeded`
|
|
789
|
-
retryable: status === 0 || status >= 500 || status === 429 && body.code !== "quota_exceeded"
|
|
842
|
+
// `daily_limit` (y el viejo `quota_exceeded`) son 429 que no se arreglan reintentando: hasta mañana.
|
|
843
|
+
retryable: status === 0 || status >= 500 || status === 429 && body.code !== "quota_exceeded" && body.code !== "daily_limit"
|
|
790
844
|
});
|
|
791
845
|
this.kind = "unknown";
|
|
792
846
|
this.name = "PartnerApiError";
|
|
@@ -889,6 +943,29 @@ var RateLimitError = class extends QuotaError {
|
|
|
889
943
|
this.kind = "rate_limit";
|
|
890
944
|
}
|
|
891
945
|
};
|
|
946
|
+
var DailyLimitError = class extends QuotaError {
|
|
947
|
+
constructor() {
|
|
948
|
+
super(...arguments);
|
|
949
|
+
this.name = "DailyLimitError";
|
|
950
|
+
this.kind = "daily_limit";
|
|
951
|
+
}
|
|
952
|
+
get sentToday() {
|
|
953
|
+
return this.body.sentToday;
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
var InsufficientBalanceError = class extends PartnerApiError {
|
|
957
|
+
constructor() {
|
|
958
|
+
super(...arguments);
|
|
959
|
+
this.name = "InsufficientBalanceError";
|
|
960
|
+
this.kind = "insufficient_balance";
|
|
961
|
+
}
|
|
962
|
+
get balanceCents() {
|
|
963
|
+
return this.body.balanceCents;
|
|
964
|
+
}
|
|
965
|
+
get requiredCents() {
|
|
966
|
+
return this.body.requiredCents;
|
|
967
|
+
}
|
|
968
|
+
};
|
|
892
969
|
var NetworkError = class extends PartnerApiError {
|
|
893
970
|
constructor(message, cause) {
|
|
894
971
|
super(0, message, {}, { cause });
|
|
@@ -907,10 +984,13 @@ function errorFor(status, body, fallback, meta = {}) {
|
|
|
907
984
|
const msg = body.error || fallback;
|
|
908
985
|
if (body.code === "reputation_hold") return new ReputationHoldError(status, msg, body, meta);
|
|
909
986
|
if (body.code === "recipient_suppressed") return new RecipientSuppressedError(status, msg, body, meta);
|
|
987
|
+
if (status === 429 && body.code === "daily_limit") return new DailyLimitError(status, msg, body, meta);
|
|
910
988
|
if (status === 429 && body.code !== "quota_exceeded") return new RateLimitError(status, msg, body, meta);
|
|
911
989
|
switch (status) {
|
|
912
990
|
case 401:
|
|
913
991
|
return new AuthError(status, msg, body, meta);
|
|
992
|
+
case 402:
|
|
993
|
+
return new InsufficientBalanceError(status, msg, body, meta);
|
|
914
994
|
case 403:
|
|
915
995
|
return new ForbiddenError(status, msg, body, meta);
|
|
916
996
|
case 404:
|
|
@@ -1111,6 +1191,26 @@ var MailMaskPartner = class {
|
|
|
1111
1191
|
async batchStatus(externalId, batchId) {
|
|
1112
1192
|
return (await this.request("GET", routePath(PARTNER_ROUTES.getBatch.path, { externalId, batchId }))).data;
|
|
1113
1193
|
}
|
|
1194
|
+
/** Saldo prepagado: saldo, precios, última recarga y consumo del mes (centavos MXN). */
|
|
1195
|
+
async balance() {
|
|
1196
|
+
return (await this.request("GET", PARTNER_ROUTES.balance.path)).data;
|
|
1197
|
+
}
|
|
1198
|
+
/** Movimientos del saldo, del más nuevo al más viejo; pagina con `nextCursor`. */
|
|
1199
|
+
async balanceMovements(query = {}) {
|
|
1200
|
+
const q = new URLSearchParams();
|
|
1201
|
+
if (query.cursor) q.set("cursor", query.cursor);
|
|
1202
|
+
if (query.limit !== void 0) q.set("limit", String(query.limit));
|
|
1203
|
+
if (query.kind?.length) q.set("kind", query.kind.join(","));
|
|
1204
|
+
const qs = q.toString();
|
|
1205
|
+
return (await this.request("GET", `${PARTNER_ROUTES.balanceMovements.path}${qs ? `?${qs}` : ""}`)).data;
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Liga de MercadoPago para recargar saldo (pago único, $500 a $100,000 MXN). El saldo sube cuando
|
|
1209
|
+
* MercadoPago confirma el pago, no al crear la liga. Abre `initPoint` en el navegador de quien paga.
|
|
1210
|
+
*/
|
|
1211
|
+
async topup(input) {
|
|
1212
|
+
return (await this.request("POST", PARTNER_ROUTES.topup.path, input)).data;
|
|
1213
|
+
}
|
|
1114
1214
|
/** Uso del mes (`YYYY-MM`, default el mes en curso UTC): tenants activos y envíos. */
|
|
1115
1215
|
async usage(opts = {}) {
|
|
1116
1216
|
const q = opts.month ? `?month=${encodeURIComponent(opts.month)}` : "";
|
|
@@ -1315,6 +1415,10 @@ var MailMaskPartnerAdmin = class {
|
|
|
1315
1415
|
resumeTenant(partnerId, externalId) {
|
|
1316
1416
|
return this.req("resumeTenant", { partnerId, externalId });
|
|
1317
1417
|
}
|
|
1418
|
+
/** Sólo MailMask: tope diario fijo del tenant, o `null` para volver al calentamiento. */
|
|
1419
|
+
setTenantDailyLimit(partnerId, externalId, limit) {
|
|
1420
|
+
return this.req("setTenantDailyLimit", { partnerId, externalId }, { limit });
|
|
1421
|
+
}
|
|
1318
1422
|
/**
|
|
1319
1423
|
* Sólo MailMask (admin): crea o adopta la hosted zone de la zona de respaldo (`marketing.denik.me`)
|
|
1320
1424
|
* y devuelve los NS que el partner tiene que delegar. Idempotente con la misma zona.
|
|
@@ -1326,6 +1430,18 @@ var MailMaskPartnerAdmin = class {
|
|
|
1326
1430
|
getFallbackZone(partnerId) {
|
|
1327
1431
|
return this.req("getFallbackZone", { partnerId });
|
|
1328
1432
|
}
|
|
1433
|
+
/** Saldo del partner con sus últimos 20 movimientos (dueño o MailMask). */
|
|
1434
|
+
getBalance(partnerId) {
|
|
1435
|
+
return this.req("getBalance", { partnerId });
|
|
1436
|
+
}
|
|
1437
|
+
/** Sólo MailMask (admin): abono (o cargo, negativo) manual. Con la misma `idempotencyKey` no se repite. */
|
|
1438
|
+
adjustBalance(partnerId, input) {
|
|
1439
|
+
return this.req("adjustBalance", { partnerId }, input);
|
|
1440
|
+
}
|
|
1441
|
+
/** Liga de MercadoPago para recargar, con la llave del dueño. */
|
|
1442
|
+
topup(partnerId, input) {
|
|
1443
|
+
return this.req("topup", { partnerId }, input);
|
|
1444
|
+
}
|
|
1329
1445
|
listCredentials(partnerId) {
|
|
1330
1446
|
return this.req("listCredentials", { partnerId });
|
|
1331
1447
|
}
|
|
@@ -1344,11 +1460,15 @@ var MailMaskPartnerAdmin = class {
|
|
|
1344
1460
|
};
|
|
1345
1461
|
export {
|
|
1346
1462
|
AuthError,
|
|
1463
|
+
BALANCE_LOW_FRACTION,
|
|
1464
|
+
BALANCE_MOVEMENT_KINDS,
|
|
1347
1465
|
ConflictError,
|
|
1348
1466
|
DOMAIN_CLAIM_TTL_DAYS,
|
|
1467
|
+
DailyLimitError,
|
|
1349
1468
|
EXTERNAL_ID_RE,
|
|
1350
1469
|
FALLBACK_LABEL_RE,
|
|
1351
1470
|
ForbiddenError,
|
|
1471
|
+
InsufficientBalanceError,
|
|
1352
1472
|
MAX_BATCH_RECIPIENTS,
|
|
1353
1473
|
MAX_TAGS,
|
|
1354
1474
|
MAX_TEMPLATE_VARS,
|
|
@@ -1365,9 +1485,14 @@ export {
|
|
|
1365
1485
|
PARTNER_API_PREFIX,
|
|
1366
1486
|
PARTNER_ERROR_STATUS,
|
|
1367
1487
|
PARTNER_HEADERS,
|
|
1488
|
+
PARTNER_PRICE_PER_EMAIL_CENTS,
|
|
1368
1489
|
PARTNER_ROUTES,
|
|
1369
1490
|
PARTNER_SIGNATURE_TOLERANCE_S,
|
|
1491
|
+
PARTNER_TENANT_MONTH_CENTS,
|
|
1492
|
+
PARTNER_TOPUP_MAX_CENTS,
|
|
1493
|
+
PARTNER_TOPUP_MIN_CENTS,
|
|
1370
1494
|
PARTNER_WEBHOOK_EVENTS,
|
|
1495
|
+
PARTNER_WELCOME_CENTS,
|
|
1371
1496
|
PartnerApiError,
|
|
1372
1497
|
PausedError,
|
|
1373
1498
|
QuotaError,
|
|
@@ -1378,12 +1503,16 @@ export {
|
|
|
1378
1503
|
STRICT_FIRST_DAYS,
|
|
1379
1504
|
TAG_RE,
|
|
1380
1505
|
TEMPLATE_VAR_NAME_RE,
|
|
1506
|
+
TENANT_DAILY_LIMIT_MAX,
|
|
1507
|
+
TENANT_DAILY_LIMIT_START,
|
|
1508
|
+
TENANT_WARMUP_STEP_DAYS,
|
|
1381
1509
|
TimeoutError,
|
|
1382
1510
|
UNSUBSCRIBE_URL_VAR,
|
|
1383
1511
|
ValidationError,
|
|
1384
1512
|
WEBHOOK_HEADERS,
|
|
1385
1513
|
WEBHOOK_TOLERANCE_MS,
|
|
1386
1514
|
createWebhookHandler,
|
|
1515
|
+
estimateSendDays,
|
|
1387
1516
|
isValidExternalId,
|
|
1388
1517
|
isValidFallbackLabel,
|
|
1389
1518
|
partnerSignatureBase,
|
|
@@ -1396,5 +1525,6 @@ export {
|
|
|
1396
1525
|
templateVarsProblem,
|
|
1397
1526
|
verifyWebhook,
|
|
1398
1527
|
verifyWebhookSignature,
|
|
1528
|
+
warmupLimit,
|
|
1399
1529
|
webhookSignatureBase
|
|
1400
1530
|
};
|
|
@@ -59,7 +59,17 @@ 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
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* Saldo prepagado insuficiente (con `balanceCents` y `requiredCents`). No se reintenta: recarga
|
|
64
|
+
* con `POST /topup`.
|
|
65
|
+
*/
|
|
66
|
+
readonly insufficient_balance: 402;
|
|
67
|
+
/**
|
|
68
|
+
* El tenant llegó a su límite del día (con `limit`, `sentToday` y `resetsAt`, la medianoche UTC).
|
|
69
|
+
* Sólo en envíos sueltos: un lote no falla, espera al día siguiente (`batch.throttled`).
|
|
70
|
+
*/
|
|
71
|
+
readonly daily_limit: 429;
|
|
72
|
+
/** Ya no sale desde la 0.8 (no hay tope mensual); se deja para clientes viejos. */
|
|
63
73
|
readonly quota_exceeded: 429;
|
|
64
74
|
/** Demasiadas peticiones; reintenta tras `Retry-After`. */
|
|
65
75
|
readonly rate_limited: 429;
|
|
@@ -72,9 +82,13 @@ export interface PartnerErrorBody {
|
|
|
72
82
|
code: PartnerErrorCode;
|
|
73
83
|
/** Sólo en `invalid_request`: el campo que falló, si aplica. */
|
|
74
84
|
field?: string;
|
|
75
|
-
/** Sólo en `
|
|
85
|
+
/** Sólo en `daily_limit`: el límite de hoy, lo enviado y cuándo se reinicia (medianoche UTC). */
|
|
76
86
|
limit?: number;
|
|
87
|
+
sentToday?: number;
|
|
77
88
|
resetsAt?: string;
|
|
89
|
+
/** Sólo en `insufficient_balance`: saldo actual y lo que pedía el envío (centavos MXN). */
|
|
90
|
+
balanceCents?: number;
|
|
91
|
+
requiredCents?: number;
|
|
78
92
|
/** Sólo en `recipient_suppressed`. */
|
|
79
93
|
reason?: SuppressionReason;
|
|
80
94
|
stream?: MessageStream;
|
|
@@ -104,7 +118,38 @@ export interface Tenant {
|
|
|
104
118
|
lastSendAt: string | null;
|
|
105
119
|
createdAt: string;
|
|
106
120
|
updatedAt: string;
|
|
121
|
+
/** Límite diario con calentamiento. Ver `TenantSending`. */
|
|
122
|
+
sending: TenantSending;
|
|
107
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Límite diario por tenant que crece solo, como el calentamiento de SES: empieza en
|
|
126
|
+
* `TENANT_DAILY_LIMIT_START` al primer envío y se duplica cada `TENANT_WARMUP_STEP_DAYS` días sanos
|
|
127
|
+
* (rebotes < 2 %, quejas < 0.1 %) hasta `TENANT_DAILY_LIMIT_MAX`. Una pausa por reputación lo
|
|
128
|
+
* regresa al inicio al reanudar. No hay tope mensual: el saldo es el otro freno.
|
|
129
|
+
*/
|
|
130
|
+
export declare const TENANT_DAILY_LIMIT_START = 2000;
|
|
131
|
+
export declare const TENANT_DAILY_LIMIT_MAX = 50000;
|
|
132
|
+
export declare const TENANT_WARMUP_STEP_DAYS = 7;
|
|
133
|
+
/** El límite del escalón `step` (0 = inicio). */
|
|
134
|
+
export declare function warmupLimit(step: number): number;
|
|
135
|
+
export interface TenantSending {
|
|
136
|
+
/** Correos que puede mandar hoy (UTC) en total. */
|
|
137
|
+
dailyLimit: number;
|
|
138
|
+
sentToday: number;
|
|
139
|
+
/** Medianoche UTC siguiente. */
|
|
140
|
+
resetsAt: string;
|
|
141
|
+
/** Cuándo sube al siguiente escalón si sigue sano; null = ya en el máximo, con tope fijo o sin envíos aún. */
|
|
142
|
+
nextStepAt: string | null;
|
|
143
|
+
/** El límite que tendrá en `nextStepAt`; null si no sube. */
|
|
144
|
+
nextStepLimit: number | null;
|
|
145
|
+
/** true = MailMask fijó el límite a mano (no se calienta). */
|
|
146
|
+
fixed: boolean;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Días (contando hoy) que tarda en salir un lote de `recipients` correos si los escalones llegan a
|
|
150
|
+
* tiempo. 1 = sale hoy. Sirve para avisar «se manda en N días» antes de encolar.
|
|
151
|
+
*/
|
|
152
|
+
export declare function estimateSendDays(s: TenantSending, recipients: number): number;
|
|
108
153
|
/** `PUT /tenants/:externalId` — crea o actualiza. 201 si lo creó, 200 si ya existía. */
|
|
109
154
|
export interface PutTenantRequest {
|
|
110
155
|
name?: string;
|
|
@@ -343,6 +388,11 @@ export interface BatchStatus {
|
|
|
343
388
|
lastError: string | null;
|
|
344
389
|
createdAt: string;
|
|
345
390
|
completedAt: string | null;
|
|
391
|
+
/**
|
|
392
|
+
* El lote llegó al límite diario del tenant y sigue solo a esta hora (medianoche UTC); null si no
|
|
393
|
+
* está esperando por el límite. Mientras espera, el lote no caduca.
|
|
394
|
+
*/
|
|
395
|
+
waitingUntil: string | null;
|
|
346
396
|
/**
|
|
347
397
|
* Sólo en modo de prueba: el lote se valida y responde `completed` al instante (los suprimidos en
|
|
348
398
|
* `skippedSuppressed`, el resto en `sent`), con `batchId` `test_…` que no se guarda (su GET da 404).
|
|
@@ -360,6 +410,8 @@ export interface TenantUsage {
|
|
|
360
410
|
bounced: number;
|
|
361
411
|
complained: number;
|
|
362
412
|
unsubscribed: number;
|
|
413
|
+
/** Límite diario de hoy; falta si el tenant ya se borró. */
|
|
414
|
+
sending?: TenantSending;
|
|
363
415
|
}
|
|
364
416
|
/** `GET /usage?month=YYYY-MM` (default: el mes en curso, UTC). Activo = al menos un envío en el mes. */
|
|
365
417
|
export interface UsageResponse {
|
|
@@ -368,6 +420,120 @@ export interface UsageResponse {
|
|
|
368
420
|
sent: number;
|
|
369
421
|
tenants: TenantUsage[];
|
|
370
422
|
}
|
|
423
|
+
/**
|
|
424
|
+
* El partner paga por adelantado: cada correo y la cuota mensual de cada tenant activo se
|
|
425
|
+
* descuentan de un saldo que se recarga con MercadoPago (`POST /topup`). Todo en centavos de MXN.
|
|
426
|
+
*/
|
|
427
|
+
/** Precio por correo enviado (por destinatario). */
|
|
428
|
+
export declare const PARTNER_PRICE_PER_EMAIL_CENTS = 6;
|
|
429
|
+
/**
|
|
430
|
+
* Cuota mensual por tenant activo. Activo = envía ese mes: se descuenta con su primer envío del
|
|
431
|
+
* mes (UTC), una sola vez, así que un tenant dormido no cuesta nada.
|
|
432
|
+
*/
|
|
433
|
+
export declare const PARTNER_TENANT_MONTH_CENTS = 4900;
|
|
434
|
+
/** Saldo de bienvenida con el que nace todo partner (`adjust`, idemKey `welcome:<partnerId>`). */
|
|
435
|
+
export declare const PARTNER_WELCOME_CENTS = 50000;
|
|
436
|
+
/** Recarga mínima y máxima por pago. */
|
|
437
|
+
export declare const PARTNER_TOPUP_MIN_CENTS = 50000;
|
|
438
|
+
export declare const PARTNER_TOPUP_MAX_CENTS = 10000000;
|
|
439
|
+
/** `balance.low` sale cuando el saldo baja del 20 % de la última recarga (una vez por recarga). */
|
|
440
|
+
export declare const BALANCE_LOW_FRACTION = 0.2;
|
|
441
|
+
export type BalanceMovementKind = "topup" | "send" | "tenant_fee" | "refund" | "adjust";
|
|
442
|
+
export declare const BALANCE_MOVEMENT_KINDS: readonly BalanceMovementKind[];
|
|
443
|
+
export interface BalanceMovement {
|
|
444
|
+
id: number;
|
|
445
|
+
kind: BalanceMovementKind;
|
|
446
|
+
/** Positivo = abono (recarga, devolución, ajuste a favor); negativo = cargo. */
|
|
447
|
+
amountCents: number;
|
|
448
|
+
balanceAfterCents: number;
|
|
449
|
+
/** Tenant del cargo (`send`, `tenant_fee`, `refund`); null en recargas y ajustes. */
|
|
450
|
+
externalId: string | null;
|
|
451
|
+
/** Referencia: `batch:<id>`, mes de la cuota (`YYYY-MM`), pago de MercadoPago… */
|
|
452
|
+
ref: string | null;
|
|
453
|
+
note: string | null;
|
|
454
|
+
createdAt: string;
|
|
455
|
+
}
|
|
456
|
+
/** `GET /balance` */
|
|
457
|
+
export interface BalanceResponse {
|
|
458
|
+
currency: "MXN";
|
|
459
|
+
/** Sin saldo suficiente el envío se rechaza (402 `insufficient_balance`) y el lote espera. Siempre. */
|
|
460
|
+
balanceCents: number;
|
|
461
|
+
prices: {
|
|
462
|
+
perEmailCents: number;
|
|
463
|
+
tenantMonthCents: number;
|
|
464
|
+
topupMinCents: number;
|
|
465
|
+
};
|
|
466
|
+
lastTopup: {
|
|
467
|
+
amountCents: number;
|
|
468
|
+
at: string;
|
|
469
|
+
} | null;
|
|
470
|
+
/** Debajo de esto sale `balance.low`; null sin recargas. */
|
|
471
|
+
lowBalanceThresholdCents: number | null;
|
|
472
|
+
/** Mes en curso (UTC). */
|
|
473
|
+
month: string;
|
|
474
|
+
/** Tenants que enviaron este mes (la misma cuenta que `/usage`): cada uno paga la cuota del mes. */
|
|
475
|
+
activeTenants: number;
|
|
476
|
+
/** Correos cobrados en el mes (sin los devueltos). */
|
|
477
|
+
monthEmails: number;
|
|
478
|
+
/** Lo gastado en el mes: correos + cuotas − devoluciones. */
|
|
479
|
+
monthSpentCents: number;
|
|
480
|
+
}
|
|
481
|
+
/** `GET /balance/movements?cursor=&limit=&kind=topup,adjust` — del más nuevo al más viejo. */
|
|
482
|
+
export interface BalanceMovementsQuery {
|
|
483
|
+
cursor?: string;
|
|
484
|
+
/** 1–200, default 50. */
|
|
485
|
+
limit?: number;
|
|
486
|
+
kind?: BalanceMovementKind[];
|
|
487
|
+
}
|
|
488
|
+
export interface BalanceMovementsResponse {
|
|
489
|
+
movements: BalanceMovement[];
|
|
490
|
+
nextCursor: string | null;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* `POST /topup` — liga de pago único de MercadoPago. El saldo sube cuando MercadoPago confirma
|
|
494
|
+
* el pago (webhook), no al crear la liga. `returnUrl` (https) es a dónde regresa el navegador.
|
|
495
|
+
*/
|
|
496
|
+
export interface TopupRequest {
|
|
497
|
+
/** Pesos MXN enteros, 500 a 100,000. */
|
|
498
|
+
amountMxn: number;
|
|
499
|
+
returnUrl?: string;
|
|
500
|
+
}
|
|
501
|
+
export interface TopupResponse {
|
|
502
|
+
topupId: string;
|
|
503
|
+
amountCents: number;
|
|
504
|
+
/** Abre esto en el navegador de quien paga. */
|
|
505
|
+
initPoint: string;
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* `GET /api/partners/:partnerId/balance` (dueño o MailMask): el saldo, sus últimos 20 movimientos,
|
|
509
|
+
* el partner (con `tenants` y `ownerEmail`) y si quien mira es de MailMask (ve abonar y el cobro).
|
|
510
|
+
*/
|
|
511
|
+
/** Un tenant en la ficha: los 50 que enviaron más recientemente, con su límite de hoy. */
|
|
512
|
+
export interface PartnerDetailTenant {
|
|
513
|
+
externalId: string;
|
|
514
|
+
name: string | null;
|
|
515
|
+
status: TenantStatus;
|
|
516
|
+
lastSendAt: string | null;
|
|
517
|
+
sending: TenantSending;
|
|
518
|
+
}
|
|
519
|
+
export type PartnerBalanceDetail = BalanceResponse & {
|
|
520
|
+
movements: BalanceMovement[];
|
|
521
|
+
partner: PartnerSummary;
|
|
522
|
+
tenants: PartnerDetailTenant[];
|
|
523
|
+
staff: boolean;
|
|
524
|
+
};
|
|
525
|
+
/** `PUT /api/partners/:partnerId/tenants/:externalId/daily-limit` — sólo MailMask: tope fijo o `null` (vuelve al calentamiento). */
|
|
526
|
+
export interface SetTenantDailyLimitRequest {
|
|
527
|
+
limit: number | null;
|
|
528
|
+
}
|
|
529
|
+
/** `POST /api/partners/:partnerId/balance/adjust` — sólo MailMask (admin): abono o cargo manual. */
|
|
530
|
+
export interface AdjustBalanceRequest {
|
|
531
|
+
/** Centavos MXN, distinto de 0 (negativo = cargo). */
|
|
532
|
+
amountCents: number;
|
|
533
|
+
note: string;
|
|
534
|
+
/** Repetir con la misma llave no vuelve a abonar. */
|
|
535
|
+
idempotencyKey?: string;
|
|
536
|
+
}
|
|
371
537
|
/** `GET /webhook` */
|
|
372
538
|
export interface WebhookConfig {
|
|
373
539
|
url: string | null;
|
|
@@ -401,7 +567,7 @@ export interface RotateWebhookSecretResponse {
|
|
|
401
567
|
export interface TestWebhookResponse {
|
|
402
568
|
deliveryId: string;
|
|
403
569
|
}
|
|
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"];
|
|
570
|
+
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", "batch.throttled"];
|
|
405
571
|
export type PartnerWebhookEvent = typeof PARTNER_WEBHOOK_EVENTS[number];
|
|
406
572
|
/** Headers de cada entrega al webhook del partner (los mismos que los webhooks de dominio). */
|
|
407
573
|
export declare const WEBHOOK_HEADERS: {
|
|
@@ -479,9 +645,23 @@ export interface PartnerEventData {
|
|
|
479
645
|
domain: string;
|
|
480
646
|
mailFromVerified: boolean;
|
|
481
647
|
};
|
|
648
|
+
/** El saldo prepagado bajó del 20 % de la última recarga. `externalId` va null. */
|
|
649
|
+
"balance.low": {
|
|
650
|
+
balanceCents: number;
|
|
651
|
+
thresholdCents: number;
|
|
652
|
+
lastTopupCents: number;
|
|
653
|
+
};
|
|
654
|
+
/** Un lote llegó al límite diario del tenant: sigue solo en `resumesAt`. Una vez por lote. */
|
|
655
|
+
"batch.throttled": {
|
|
656
|
+
batchId: string;
|
|
657
|
+
sent: number;
|
|
658
|
+
total: number;
|
|
659
|
+
dailyLimit: number;
|
|
660
|
+
resumesAt: string;
|
|
661
|
+
};
|
|
482
662
|
ping: Record<string, never>;
|
|
483
663
|
}
|
|
484
|
-
/** Sobre de cada evento. `externalId` es null
|
|
664
|
+
/** Sobre de cada evento. `externalId` es null en `ping` y `balance.low`. */
|
|
485
665
|
export interface PartnerWebhookPayload<E extends keyof PartnerEventData = keyof PartnerEventData> {
|
|
486
666
|
/** Id de la entrega (igual que `X-MailMask-Delivery`); úsalo para descartar duplicados. */
|
|
487
667
|
id: string;
|
|
@@ -498,10 +678,11 @@ export interface PartnerWebhookPayload<E extends keyof PartnerEventData = keyof
|
|
|
498
678
|
export type ReputationPolicy = "standard" | "strict";
|
|
499
679
|
/** Primer mes del partner: sus tenants van en Strict salvo que MailMask fije otra política. */
|
|
500
680
|
export declare const STRICT_FIRST_DAYS = 30;
|
|
501
|
-
/** `GET /api/partners` — los partners de la cuenta. */
|
|
681
|
+
/** `GET /api/partners` — los partners de la cuenta. MailMask (admin) con `?all=1`: todos, con `tenants`. */
|
|
502
682
|
export interface PartnerSummary {
|
|
503
683
|
id: string;
|
|
504
684
|
name: string;
|
|
685
|
+
/** `sendsPerTenantMonth` ya no frena (0.8): el límite es diario por tenant, ver `TenantSending`. */
|
|
505
686
|
plan: {
|
|
506
687
|
includedTenants: number;
|
|
507
688
|
sendsPerTenantMonth: number;
|
|
@@ -515,6 +696,15 @@ export interface PartnerSummary {
|
|
|
515
696
|
effectiveReputationPolicy: ReputationPolicy;
|
|
516
697
|
/** Zona de respaldo (`marketing.denik.me`); null = sin zona. Ver `FallbackZone`. */
|
|
517
698
|
fallbackZone: string | null;
|
|
699
|
+
/** Saldo prepagado (centavos MXN). Ver `BalanceResponse`. */
|
|
700
|
+
balanceCents: number;
|
|
701
|
+
/** Sólo en `GET /api/partners?all=1` (MailMask, admin): tenants totales y activos del mes. */
|
|
702
|
+
tenants?: {
|
|
703
|
+
total: number;
|
|
704
|
+
activeThisMonth: number;
|
|
705
|
+
};
|
|
706
|
+
/** Sólo en `?all=1` (admin): cuenta dueña. */
|
|
707
|
+
ownerEmail?: string;
|
|
518
708
|
}
|
|
519
709
|
/** `POST /api/partners` — alta (sólo MailMask, admin). 201 con `PartnerSummary`. */
|
|
520
710
|
export interface CreatePartnerRequest {
|
|
@@ -630,6 +820,18 @@ export declare const PARTNER_ROUTES: {
|
|
|
630
820
|
readonly method: "GET";
|
|
631
821
|
readonly path: "/usage";
|
|
632
822
|
};
|
|
823
|
+
readonly balance: {
|
|
824
|
+
readonly method: "GET";
|
|
825
|
+
readonly path: "/balance";
|
|
826
|
+
};
|
|
827
|
+
readonly balanceMovements: {
|
|
828
|
+
readonly method: "GET";
|
|
829
|
+
readonly path: "/balance/movements";
|
|
830
|
+
};
|
|
831
|
+
readonly topup: {
|
|
832
|
+
readonly method: "POST";
|
|
833
|
+
readonly path: "/topup";
|
|
834
|
+
};
|
|
633
835
|
readonly getWebhook: {
|
|
634
836
|
readonly method: "GET";
|
|
635
837
|
readonly path: "/webhook";
|
|
@@ -676,6 +878,26 @@ export declare const PARTNER_ADMIN_ROUTES: {
|
|
|
676
878
|
readonly method: "GET";
|
|
677
879
|
readonly path: "/api/partners/:partnerId/fallback-zone";
|
|
678
880
|
};
|
|
881
|
+
/** El saldo del partner (dueño o MailMask). */
|
|
882
|
+
readonly getBalance: {
|
|
883
|
+
readonly method: "GET";
|
|
884
|
+
readonly path: "/api/partners/:partnerId/balance";
|
|
885
|
+
};
|
|
886
|
+
/** Sólo MailMask (admin): abono o cargo manual (`adjust`). */
|
|
887
|
+
readonly adjustBalance: {
|
|
888
|
+
readonly method: "POST";
|
|
889
|
+
readonly path: "/api/partners/:partnerId/balance/adjust";
|
|
890
|
+
};
|
|
891
|
+
/** Recarga con MercadoPago desde la sesión del dueño (misma respuesta que `POST /topup`). */
|
|
892
|
+
readonly topup: {
|
|
893
|
+
readonly method: "POST";
|
|
894
|
+
readonly path: "/api/partners/:partnerId/topup";
|
|
895
|
+
};
|
|
896
|
+
/** Sólo MailMask: fija (o quita, con `null`) el límite diario de un tenant. Devuelve el `Tenant`. */
|
|
897
|
+
readonly setTenantDailyLimit: {
|
|
898
|
+
readonly method: "PUT";
|
|
899
|
+
readonly path: "/api/partners/:partnerId/tenants/:externalId/daily-limit";
|
|
900
|
+
};
|
|
679
901
|
readonly listCredentials: {
|
|
680
902
|
readonly method: "GET";
|
|
681
903
|
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" | "daily_limit" | "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,25 @@ export declare class RateLimitError extends QuotaError {
|
|
|
100
100
|
name: string;
|
|
101
101
|
kind: PartnerErrorKind;
|
|
102
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* 429 `daily_limit`: el tenant llegó a su límite del día (crece solo con buena reputación). NO se
|
|
105
|
+
* reintenta: vuelve a intentar después de `resetsAt` (medianoche UTC) o manda un `batch`, que espera
|
|
106
|
+
* solo. Trae `limit` y `sentToday`.
|
|
107
|
+
*/
|
|
108
|
+
export declare class DailyLimitError extends QuotaError {
|
|
109
|
+
name: string;
|
|
110
|
+
kind: PartnerErrorKind;
|
|
111
|
+
get sentToday(): number | undefined;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 402 `insufficient_balance`: el saldo prepagado no alcanza. No se reintenta: recarga con `partner.topup()`. Trae `balanceCents` y `requiredCents`.
|
|
115
|
+
*/
|
|
116
|
+
export declare class InsufficientBalanceError extends PartnerApiError {
|
|
117
|
+
name: string;
|
|
118
|
+
kind: PartnerErrorKind;
|
|
119
|
+
get balanceCents(): number | undefined;
|
|
120
|
+
get requiredCents(): number | undefined;
|
|
121
|
+
}
|
|
103
122
|
/** No hubo respuesta: DNS, conexión rechazada o cortada. `status` 0. */
|
|
104
123
|
export declare class NetworkError extends PartnerApiError {
|
|
105
124
|
name: string;
|
|
@@ -218,6 +237,15 @@ export declare class MailMaskPartner {
|
|
|
218
237
|
}>;
|
|
219
238
|
/** Estado de un lote (`batchId` del resultado de `batch`): envíos y, después, entregas, rebotes, quejas y bajas. */
|
|
220
239
|
batchStatus(externalId: string, batchId: string): Promise<BatchStatus>;
|
|
240
|
+
/** Saldo prepagado: saldo, precios, última recarga y consumo del mes (centavos MXN). */
|
|
241
|
+
balance(): Promise<BalanceResponse>;
|
|
242
|
+
/** Movimientos del saldo, del más nuevo al más viejo; pagina con `nextCursor`. */
|
|
243
|
+
balanceMovements(query?: BalanceMovementsQuery): Promise<BalanceMovementsResponse>;
|
|
244
|
+
/**
|
|
245
|
+
* Liga de MercadoPago para recargar saldo (pago único, $500 a $100,000 MXN). El saldo sube cuando
|
|
246
|
+
* MercadoPago confirma el pago, no al crear la liga. Abre `initPoint` en el navegador de quien paga.
|
|
247
|
+
*/
|
|
248
|
+
topup(input: TopupRequest): Promise<TopupResponse>;
|
|
221
249
|
/** Uso del mes (`YYYY-MM`, default el mes en curso UTC): tenants activos y envíos. */
|
|
222
250
|
usage(opts?: {
|
|
223
251
|
month?: string;
|
|
@@ -354,6 +382,8 @@ export declare class MailMaskPartnerAdmin {
|
|
|
354
382
|
setReputationPolicy(partnerId: string, policy: ReputationPolicy | null): Promise<PartnerSummary>;
|
|
355
383
|
/** Sólo MailMask (admin): levanta cualquier pausa, también la de reputación. */
|
|
356
384
|
resumeTenant(partnerId: string, externalId: string): Promise<Tenant>;
|
|
385
|
+
/** Sólo MailMask: tope diario fijo del tenant, o `null` para volver al calentamiento. */
|
|
386
|
+
setTenantDailyLimit(partnerId: string, externalId: string, limit: number | null): Promise<Tenant>;
|
|
357
387
|
/**
|
|
358
388
|
* Sólo MailMask (admin): crea o adopta la hosted zone de la zona de respaldo (`marketing.denik.me`)
|
|
359
389
|
* y devuelve los NS que el partner tiene que delegar. Idempotente con la misma zona.
|
|
@@ -361,6 +391,12 @@ export declare class MailMaskPartnerAdmin {
|
|
|
361
391
|
setFallbackZone(partnerId: string, zone: string): Promise<FallbackZone>;
|
|
362
392
|
/** La zona de respaldo y si el DNS público ya ve la delegación. */
|
|
363
393
|
getFallbackZone(partnerId: string): Promise<FallbackZone>;
|
|
394
|
+
/** Saldo del partner con sus últimos 20 movimientos (dueño o MailMask). */
|
|
395
|
+
getBalance(partnerId: string): Promise<PartnerBalanceDetail>;
|
|
396
|
+
/** Sólo MailMask (admin): abono (o cargo, negativo) manual. Con la misma `idempotencyKey` no se repite. */
|
|
397
|
+
adjustBalance(partnerId: string, input: AdjustBalanceRequest): Promise<PartnerBalanceDetail>;
|
|
398
|
+
/** Liga de MercadoPago para recargar, con la llave del dueño. */
|
|
399
|
+
topup(partnerId: string, input: TopupRequest): Promise<TopupResponse>;
|
|
364
400
|
listCredentials(partnerId: string): Promise<ListCredentialsResponse>;
|
|
365
401
|
/** El secreto `mms_…` viene UNA vez en la respuesta. */
|
|
366
402
|
createCredential(partnerId: string, input?: CreateCredentialRequest): Promise<CreateCredentialResponse>;
|