Back off SMS queue after gateway throttling
Browse files- src/jobs/expireTokens.job.js +5 -1
- src/services/sms.js +14 -6
src/jobs/expireTokens.job.js
CHANGED
|
@@ -78,7 +78,11 @@ async function expireTokens() {
|
|
| 78 |
if (token.customer_phone) {
|
| 79 |
sms.sendSMS(
|
| 80 |
token.customer_phone,
|
| 81 |
-
messages.tokenExpired(token.device_name, token.plan_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
).catch((err) => {
|
| 83 |
console.error(`[expireTokens] Expiry SMS failed for ${token.code}:`, err.message);
|
| 84 |
});
|
|
|
|
| 78 |
if (token.customer_phone) {
|
| 79 |
sms.sendSMS(
|
| 80 |
token.customer_phone,
|
| 81 |
+
messages.tokenExpired(token.device_name, token.plan_name),
|
| 82 |
+
{
|
| 83 |
+
ttl: 6 * 60 * 60,
|
| 84 |
+
priority: sms.priorities.low,
|
| 85 |
+
}
|
| 86 |
).catch((err) => {
|
| 87 |
console.error(`[expireTokens] Expiry SMS failed for ${token.code}:`, err.message);
|
| 88 |
});
|
src/services/sms.js
CHANGED
|
@@ -2,16 +2,17 @@ const axios = require('axios');
|
|
| 2 |
const crypto = require('crypto');
|
| 3 |
const db = require('../config/db');
|
| 4 |
|
| 5 |
-
const SEND_INTERVAL_MS = Number(process.env.SMS_SEND_INTERVAL_MS ||
|
| 6 |
-
const MAX_ATTEMPTS = Number(process.env.SMS_MAX_ATTEMPTS ||
|
| 7 |
-
const RATE_LIMIT_BACKOFF_MS = Number(process.env.SMS_RATE_LIMIT_BACKOFF_MS ||
|
| 8 |
-
const MAX_BACKOFF_MS = Number(process.env.SMS_MAX_BACKOFF_MS ||
|
| 9 |
const DEFAULT_TTL_SECONDS = 24 * 60 * 60;
|
| 10 |
const DEFAULT_PRIORITY = 0;
|
| 11 |
const HIGH_PRIORITY = 100;
|
| 12 |
|
| 13 |
let sendQueue = Promise.resolve();
|
| 14 |
let lastSendAt = 0;
|
|
|
|
| 15 |
|
| 16 |
function normalizePhone(phone) {
|
| 17 |
const d = phone.replace(/\D/g, '');
|
|
@@ -46,6 +47,9 @@ function retryAfterMs(err) {
|
|
| 46 |
}
|
| 47 |
|
| 48 |
async function waitForTurn() {
|
|
|
|
|
|
|
|
|
|
| 49 |
const elapsed = Date.now() - lastSendAt;
|
| 50 |
const waitMs = Math.max(0, SEND_INTERVAL_MS - elapsed);
|
| 51 |
if (waitMs > 0) await delay(waitMs);
|
|
@@ -108,11 +112,15 @@ async function sendWithRetry(to, message, options) {
|
|
| 108 |
const statusCode = err.response?.status;
|
| 109 |
const isRateLimited = statusCode === 429;
|
| 110 |
const canRetry = isRateLimited && attempt < MAX_ATTEMPTS;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
if (!canRetry) break;
|
| 113 |
|
| 114 |
-
const gatewayWait = retryAfterMs(err);
|
| 115 |
-
const backoffMs = Math.min(gatewayWait || RATE_LIMIT_BACKOFF_MS * attempt, MAX_BACKOFF_MS);
|
| 116 |
console.warn(`[SMS] Rate limited for ${to}; retrying attempt ${attempt + 1}/${MAX_ATTEMPTS} in ${backoffMs}ms`);
|
| 117 |
await delay(backoffMs);
|
| 118 |
}
|
|
|
|
| 2 |
const crypto = require('crypto');
|
| 3 |
const db = require('../config/db');
|
| 4 |
|
| 5 |
+
const SEND_INTERVAL_MS = Number(process.env.SMS_SEND_INTERVAL_MS || 5000);
|
| 6 |
+
const MAX_ATTEMPTS = Number(process.env.SMS_MAX_ATTEMPTS || 4);
|
| 7 |
+
const RATE_LIMIT_BACKOFF_MS = Number(process.env.SMS_RATE_LIMIT_BACKOFF_MS || 60000);
|
| 8 |
+
const MAX_BACKOFF_MS = Number(process.env.SMS_MAX_BACKOFF_MS || 180000);
|
| 9 |
const DEFAULT_TTL_SECONDS = 24 * 60 * 60;
|
| 10 |
const DEFAULT_PRIORITY = 0;
|
| 11 |
const HIGH_PRIORITY = 100;
|
| 12 |
|
| 13 |
let sendQueue = Promise.resolve();
|
| 14 |
let lastSendAt = 0;
|
| 15 |
+
let rateLimitUntil = 0;
|
| 16 |
|
| 17 |
function normalizePhone(phone) {
|
| 18 |
const d = phone.replace(/\D/g, '');
|
|
|
|
| 47 |
}
|
| 48 |
|
| 49 |
async function waitForTurn() {
|
| 50 |
+
const cooldownMs = Math.max(0, rateLimitUntil - Date.now());
|
| 51 |
+
if (cooldownMs > 0) await delay(cooldownMs);
|
| 52 |
+
|
| 53 |
const elapsed = Date.now() - lastSendAt;
|
| 54 |
const waitMs = Math.max(0, SEND_INTERVAL_MS - elapsed);
|
| 55 |
if (waitMs > 0) await delay(waitMs);
|
|
|
|
| 112 |
const statusCode = err.response?.status;
|
| 113 |
const isRateLimited = statusCode === 429;
|
| 114 |
const canRetry = isRateLimited && attempt < MAX_ATTEMPTS;
|
| 115 |
+
const gatewayWait = retryAfterMs(err);
|
| 116 |
+
const backoffMs = Math.min(gatewayWait || RATE_LIMIT_BACKOFF_MS * attempt, MAX_BACKOFF_MS);
|
| 117 |
+
|
| 118 |
+
if (isRateLimited) {
|
| 119 |
+
rateLimitUntil = Math.max(rateLimitUntil, Date.now() + backoffMs);
|
| 120 |
+
}
|
| 121 |
|
| 122 |
if (!canRetry) break;
|
| 123 |
|
|
|
|
|
|
|
| 124 |
console.warn(`[SMS] Rate limited for ${to}; retrying attempt ${attempt + 1}/${MAX_ATTEMPTS} in ${backoffMs}ms`);
|
| 125 |
await delay(backoffMs);
|
| 126 |
}
|