Loading...
Loading...
Compare original and translation side by side
twiliotwiliotwiliotwilioTWILIO_ACCOUNT_SIDAC...TWILIO_AUTH_TOKENTWILIO_MESSAGING_SERVICE_SIDMG...TWILIO_FROM_NUMBER+14155552671.envTWILIO_ACCOUNT_SIDAC...TWILIO_AUTH_TOKENTWILIO_MESSAGING_SERVICE_SIDMG...TWILIO_FROM_NUMBER+14155552671.envapplication/x-www-form-urlencodedX-Twilio-Signatureapplication/x-www-form-urlencodedX-Twilio-SignatureMessageSidSM...MessageSidSM...MessageStatusqueuedsendingsentdeliveredundeliveredfailedsentMessageStatusqueuedsendingsentdeliveredundeliveredfailedsent<Response>
<Message>Thanks. We received your message.</Message>
</Response><Response>
<Message>Thanks. We received your message.</Message>
</Response>STOPUNSUBSCRIBESTARTSTOPUNSUBSCRIBESTARTMessageSidSmsSidMessageSidSmsSidpip install twiliofrom twilio.rest import Client
client = Client() # TWILIO_ACCOUNT_SID + TWILIO_AUTH_TOKEN from envfrom twilio.rest import Client
client = Client() # 从环境变量读取TWILIO_ACCOUNT_SID + TWILIO_AUTH_TOKEN
Source: [twilio/twilio-python — messages](https://github.com/twilio/twilio-python/blob/main/twilio/rest/api/v2010/account/message/__init__.py)
来源:[twilio/twilio-python — messages](https://github.com/twilio/twilio-python/blob/main/twilio/rest/api/v2010/account/message/__init__.py)sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg jqcurl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
node -v # v20.11.1 (or later 20.x)
npm -v # 10.2.4 (or later)sudo apt-get install -y python3.11 python3.11-venv python3-pip
python3.11 --versionnpm install -g twilio-cli@5.16.0
twilio --versioncurl -fsSL https://ngrok-agent.s3.amazonaws.com/ngrok.asc \
| sudo gpg --dearmor -o /usr/share/keyrings/ngrok.gpg
echo "deb [signed-by=/usr/share/keyrings/ngrok.gpg] https://ngrok-agent.s3.amazonaws.com buster main" \
| sudo tee /etc/apt/sources.list.d/ngrok.list
sudo apt-get update && sudo apt-get install -y ngrok
ngrok versionsudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg jqcurl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
node -v # v20.11.1(或更高20.x版本)
npm -v # 10.2.4(或更高版本)sudo apt-get install -y python3.11 python3.11-venv python3-pip
python3.11 --versionnpm install -g twilio-cli@5.16.0
twilio --versioncurl -fsSL https://ngrok-agent.s3.amazonaws.com/ngrok.asc \
| sudo gpg --dearmor -o /usr/share/keyrings/ngrok.gpg
echo "deb [signed-by=/usr/share/keyrings/ngrok.gpg] https://ngrok-agent.s3.amazonaws.com buster main" \
| sudo tee /etc/apt/sources.list.d/ngrok.list
sudo apt-get update && sudo apt-get install -y ngrok
ngrok versionsudo dnf install -y curl jq nodejs python3.11 python3.11-pip
node -v
python3.11 --versionsudo npm install -g twilio-cli@5.16.0
twilio --versionsudo dnf install -y curl jq nodejs python3.11 python3.11-pip
node -v
python3.11 --versionsudo npm install -g twilio-cli@5.16.0
twilio --versionbrew update
brew install node@20 python@3.11 jqecho 'export PATH="/opt/homebrew/opt/node@20/bin:/opt/homebrew/opt/python@3.11/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
node -v
python3.11 --versionnpm install -g twilio-cli@5.16.0
twilio --versionbrew install ngrok/ngrok/ngrok
ngrok versionbrew update
brew install node@20 python@3.11 jqecho 'export PATH="/opt/homebrew/opt/node@20/bin:/opt/homebrew/opt/python@3.11/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
node -v
python3.11 --versionnpm install -g twilio-cli@5.16.0
twilio --versionbrew install ngrok/ngrok/ngrok
ngrok versiondocker --version
docker compose versiondocker --version
docker compose versiontwilio loginexport TWILIO_ACCOUNT_SID="YOUR_ACCOUNT_SID"
export TWILIO_AUTH_TOKEN="your_auth_token"twilio api:core:accounts:fetch --sid "$TWILIO_ACCOUNT_SID"twilio loginexport TWILIO_ACCOUNT_SID="YOUR_ACCOUNT_SID"
export TWILIO_AUTH_TOKEN="your_auth_token"twilio api:core:accounts:fetch --sid "$TWILIO_ACCOUNT_SID"ngrok http 3000ngrok http 3000
Configure Twilio inbound webhook to:
- `https://.../twilio/inbound`
- Status callback to:
- `https://.../twilio/status`
---
配置Twilio入站Webhook为:
- `https://.../twilio/inbound`
- 状态回调为:
- `https://.../twilio/status`
---messagingServiceSidstatusCallbackprovideFeedback=trueimport twilio from "twilio";
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
const msg = await client.messages.create({
messagingServiceSid: process.env.TWILIO_MESSAGING_SERVICE_SID,
to: "+14155550123",
body: "Build 742 deployed. Reply STOP to opt out.",
statusCallback: "https://api.example.com/twilio/status",
provideFeedback: true,
});
console.log(msg.sid, msg.status);from twilio.rest import Client
import os
client = Client(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"])
msg = client.messages.create(
messaging_service_sid=os.environ["TWILIO_MESSAGING_SERVICE_SID"],
to="+14155550123",
body="Here is the incident screenshot.",
media_url=["https://cdn.example.com/incidents/INC-2048.png"],
status_callback="https://api.example.com/twilio/status",
)
print(msg.sid, msg.status)messagingServiceSidstatusCallbackprovideFeedback=trueimport twilio from "twilio";
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
const msg = await client.messages.create({
messagingServiceSid: process.env.TWILIO_MESSAGING_SERVICE_SID,
to: "+14155550123",
body: "Build 742 deployed. Reply STOP to opt out.",
statusCallback: "https://api.example.com/twilio/status",
provideFeedback: true,
});
console.log(msg.sid, msg.status);from twilio.rest import Client
import os
client = Client(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"])
msg = client.messages.create(
messaging_service_sid=os.environ["TWILIO_MESSAGING_SERVICE_SID"],
to="+14155550123",
body="Here is the incident screenshot.",
media_url=["https://cdn.example.com/incidents/INC-2048.png"],
status_callback="https://api.example.com/twilio/status",
)
print(msg.sid, msg.status)FromToBodyMessageSidSmsSidNumMediaMediaUrl0MediaContentType0import express from "express";
import twilio from "twilio";
const app = express();
// Twilio sends application/x-www-form-urlencoded by default
app.use(express.urlencoded({ extended: false }));
app.post("/twilio/inbound", (req, res) => {
const signature = req.header("X-Twilio-Signature") || "";
const url = "https://api.example.com/twilio/inbound"; // must match public URL exactly
const isValid = twilio.validateRequest(
process.env.TWILIO_AUTH_TOKEN,
signature,
url,
req.body
);
if (!isValid) {
return res.status(403).send("Invalid signature");
}
const from = req.body.From;
const body = (req.body.Body || "").trim();
// Fast path: respond immediately; enqueue work elsewhere
const twiml = new twilio.twiml.MessagingResponse();
if (body.toUpperCase() === "HELP") {
twiml.message("Support: https://status.example.com. Reply STOP to opt out.");
} else {
twiml.message("Received. Ticket created.");
}
res.type("text/xml").send(twiml.toString());
});
app.listen(3000);from fastapi import FastAPI, Request, Response
from twilio.request_validator import RequestValidator
from twilio.twiml.messaging_response import MessagingResponse
import os
app = FastAPI()
validator = RequestValidator(os.environ["TWILIO_AUTH_TOKEN"])
@app.post("/twilio/inbound")
async def inbound(request: Request):
form = await request.form()
signature = request.headers.get("X-Twilio-Signature", "")
url = "https://api.example.com/twilio/inbound"
if not validator.validate(url, dict(form), signature):
return Response("Invalid signature", status_code=403)
body = (form.get("Body") or "").strip()
resp = MessagingResponse()
resp.message("Received.")
return Response(str(resp), media_type="text/xml")FromToBodyMessageSidSmsSidNumMediaMediaUrl0MediaContentType0import express from "express";
import twilio from "twilio";
const app = express();
// Twilio默认发送application/x-www-form-urlencoded格式
app.use(express.urlencoded({ extended: false }));
app.post("/twilio/inbound", (req, res) => {
const signature = req.header("X-Twilio-Signature") || "";
const url = "https://api.example.com/twilio/inbound"; // 必须与公开URL完全匹配
const isValid = twilio.validateRequest(
process.env.TWILIO_AUTH_TOKEN,
signature,
url,
req.body
);
if (!isValid) {
return res.status(403).send("Invalid signature");
}
const from = req.body.From;
const body = (req.body.Body || "").trim();
// 快速路径:立即回复;将其他工作加入队列
const twiml = new twilio.twiml.MessagingResponse();
if (body.toUpperCase() === "HELP") {
twiml.message("Support: https://status.example.com. Reply STOP to opt out.");
} else {
twiml.message("Received. Ticket created.");
}
res.type("text/xml").send(twiml.toString());
});
app.listen(3000);from fastapi import FastAPI, Request, Response
from twilio.request_validator import RequestValidator
from twilio.twiml.messaging_response import MessagingResponse
import os
app = FastAPI()
validator = RequestValidator(os.environ["TWILIO_AUTH_TOKEN"])
@app.post("/twilio/inbound")
async def inbound(request: Request):
form = await request.form()
signature = request.headers.get("X-Twilio-Signature", "")
url = "https://api.example.com/twilio/inbound"
if not validator.validate(url, dict(form), signature):
return Response("Invalid signature", status_code=403)
body = (form.get("Body") or "").strip()
resp = MessagingResponse()
resp.message("Received.")
return Response(str(resp), media_type="text/xml")MessageSidMessageStatusqueuedsentdeliveredundeliveredfailedToFromErrorCode30003ErrorMessageapp.post("/twilio/status", express.urlencoded({ extended: false }), async (req, res) => {
// Validate signature same as inbound; use exact public URL
const messageSid = req.body.MessageSid;
const status = req.body.MessageStatus;
const errorCode = req.body.ErrorCode ? Number(req.body.ErrorCode) : null;
// Idempotency: upsert by messageSid + status
// Example: write to DB with unique constraint (messageSid, status)
console.log({ messageSid, status, errorCode });
res.status(204).send();
});undeliveredfailedMessageSidMessageStatusqueuedsentdeliveredundeliveredfailedToFromErrorCode30003ErrorMessageapp.post("/twilio/status", express.urlencoded({ extended: false }), async (req, res) => {
// 签名验证方式与入站Webhook相同;使用精确的公开URL
const messageSid = req.body.MessageSid;
const status = req.body.MessageStatus;
const errorCode = req.body.ErrorCode ? Number(req.body.ErrorCode) : null;
// 幂等性:通过messageSid + status进行更新插入
// 示例:写入带唯一约束(messageSid, status)的数据库
console.log({ messageSid, status, errorCode });
res.status(204).send();
});undeliveredfailedconst normalized = body.trim().toUpperCase();
const isStop = ["STOP", "STOPALL", "UNSUBSCRIBE", "CANCEL", "END", "QUIT"].includes(normalized);
const isStart = ["START", "YES", "UNSTOP"].includes(normalized);
if (isStop) {
// mark user opted out in your DB
}
if (isStart) {
// mark user opted in
}21610const normalized = body.trim().toUpperCase();
const isStop = ["STOP", "STOPALL", "UNSUBSCRIBE", "CANCEL", "END", "QUIT"].includes(normalized);
const isStart = ["START", "YES", "UNSTOP"].includes(normalized);
if (isStop) {
// 在数据库中标记用户已退订
}
if (isStart) {
// 在数据库中标记用户已重新订阅
}21610Fromtwilio api:messaging:v1:services:create \
--friendly-name "prod-notifications" \
--status-callback "https://api.example.com/twilio/status"twilio api:messaging:v1:services:phone-numbers:create \
--service-sid YOUR_MG_SID \
--phone-number-sid PN0123456789abcdef0123456789abcdeftwilio api:messaging:v1:services:update \
--sid YOUR_MG_SID \
--sticky-sender true \
--area-code-geomatch trueFromtwilio api:messaging:v1:services:create \
--friendly-name "prod-notifications" \
--status-callback "https://api.example.com/twilio/status"twilio api:messaging:v1:services:phone-numbers:create \
--service-sid YOUR_MG_SID \
--phone-number-sid PN0123456789abcdef0123456789abcdeftwilio api:messaging:v1:services:update \
--sid YOUR_MG_SID \
--sticky-sender true \
--area-code-geomatch true+1...3000330005undelivered+1...3000330005undeliveredtwilio login
twilio profiles:list
twilio profiles:use <profile>TWILIO_ACCOUNT_SID=AC... TWILIO_AUTH_TOKEN=... twilio api:core:accounts:fetch --sid AC...twilio login
twilio profiles:list
twilio profiles:use <profile>TWILIO_ACCOUNT_SID=AC... TWILIO_AUTH_TOKEN=... twilio api:core:accounts:fetch --sid AC...twilio api:core:messages:createtwilio api:core:messages:create \
--to "+14155550123" \
--from "+14155552671" \
--body "Deploy complete." \
--status-callback "https://api.example.com/twilio/status" \
--provide-feedback true \
--max-price 0.015 \
--application-sid AP0123456789abcdef0123456789abcdef--to--from--messaging-service-sid MG...--from--body--media-url--status-callback--provide-feedback--max-price--application-sidtwilio api:core:messages:create \
--to "+14155550123" \
--messaging-service-sid YOUR_MG_SID \
--body "Photo" \
--media-url "https://cdn.example.com/a.png" \
--media-url "https://cdn.example.com/b.jpg"twilio api:core:messages:fetch --sid SM0123456789abcdef0123456789abcdeftwilio api:core:messages:list --limit 50
twilio api:core:messages:list --to "+14155550123" --limit 20
twilio api:core:messages:list --from "+14155552671" --limit 20twilio api:core:messages:remove --sid SM0123456789abcdef0123456789abcdeftwilio api:core:messages:createtwilio api:core:messages:create \
--to "+14155550123" \
--from "+14155552671" \
--body "Deploy complete." \
--status-callback "https://api.example.com/twilio/status" \
--provide-feedback true \
--max-price 0.015 \
--application-sid AP0123456789abcdef0123456789abcdef--to--from--messaging-service-sid MG...--from--body--media-url--status-callback--provide-feedback--max-price--application-sidtwilio api:core:messages:create \
--to "+14155550123" \
--messaging-service-sid YOUR_MG_SID \
--body "Photo" \
--media-url "https://cdn.example.com/a.png" \
--media-url "https://cdn.example.com/b.jpg"twilio api:core:messages:fetch --sid SM0123456789abcdef0123456789abcdeftwilio api:core:messages:list --limit 50
twilio api:core:messages:list --to "+14155550123" --limit 20
twilio api:core:messages:list --from "+14155552671" --limit 20twilio api:core:messages:remove --sid SM0123456789abcdef0123456789abcdeftwilio api:messaging:v1:services:create \
--friendly-name "prod-notifications" \
--status-callback "https://api.example.com/twilio/status"twilio api:messaging:v1:services:update \
--sid YOUR_MG_SID \
--friendly-name "prod-notifications" \
--status-callback "https://api.example.com/twilio/status" \
--inbound-request-url "https://api.example.com/twilio/inbound" \
--inbound-method POSTtwilio api:messaging:v1:services:list --limit 50twilio api:messaging:v1:services:fetch --sid YOUR_MG_SIDtwilio api:messaging:v1:services:phone-numbers:list \
--service-sid YOUR_MG_SID \
--limit 50twilio api:messaging:v1:services:phone-numbers:create \
--service-sid YOUR_MG_SID \
--phone-number-sid PN0123456789abcdef0123456789abcdeftwilio api:messaging:v1:services:phone-numbers:remove \
--service-sid YOUR_MG_SID \
--sid PN0123456789abcdef0123456789abcdeftwilio api:messaging:v1:services:create \
--friendly-name "prod-notifications" \
--status-callback "https://api.example.com/twilio/status"twilio api:messaging:v1:services:update \
--sid YOUR_MG_SID \
--friendly-name "prod-notifications" \
--status-callback "https://api.example.com/twilio/status" \
--inbound-request-url "https://api.example.com/twilio/inbound" \
--inbound-method POSTtwilio api:messaging:v1:services:list --limit 50twilio api:messaging:v1:services:fetch --sid YOUR_MG_SIDtwilio api:messaging:v1:services:phone-numbers:list \
--service-sid YOUR_MG_SID \
--limit 50twilio api:messaging:v1:services:phone-numbers:create \
--service-sid YOUR_MG_SID \
--phone-number-sid PN0123456789abcdef0123456789abcdeftwilio api:messaging:v1:services:phone-numbers:remove \
--service-sid YOUR_MG_SID \
--sid PN0123456789abcdef0123456789abcdeftwilio api:core:incoming-phone-numbers:list --limit 50twilio api:core:incoming-phone-numbers:fetch --sid PN0123456789abcdef0123456789abcdeftwilio api:core:incoming-phone-numbers:update \
--sid PN0123456789abcdef0123456789abcdef \
--sms-url "https://api.example.com/twilio/inbound" \
--sms-method POST \
--sms-fallback-url "https://api.example.com/twilio/fallback" \
--sms-fallback-method POST \
--status-callback "https://api.example.com/twilio/status"twilio api:core:incoming-phone-numbers:list --limit 50twilio api:core:incoming-phone-numbers:fetch --sid PN0123456789abcdef0123456789abcdeftwilio api:core:incoming-phone-numbers:update \
--sid PN0123456789abcdef0123456789abcdef \
--sms-url "https://api.example.com/twilio/inbound" \
--sms-method POST \
--sms-fallback-url "https://api.example.com/twilio/fallback" \
--sms-fallback-method POST \
--status-callback "https://api.example.com/twilio/status"TWILIO_ACCOUNT_SIDTWILIO_AUTH_TOKENTWILIO_MESSAGING_SERVICE_SIDTWILIO_FROM_NUMBERTWILIO_STATUS_CALLBACK_URLTWILIO_INBOUND_WEBHOOK_PUBLIC_URLTWILIO_ACCOUNT_SIDTWILIO_AUTH_TOKENTWILIO_MESSAGING_SERVICE_SIDTWILIO_FROM_NUMBERTWILIO_STATUS_CALLBACK_URLTWILIO_INBOUND_WEBHOOK_PUBLIC_URL.env.env/srv/app/.envTWILIO_ACCOUNT_SID=YOUR_ACCOUNT_SID
TWILIO_AUTH_TOKEN=0123456789abcdef0123456789abcdef
TWILIO_MESSAGING_SERVICE_SID=YOUR_MG_SID
TWILIO_STATUS_CALLBACK_URL=https://api.example.com/twilio/status
TWILIO_INBOUND_WEBHOOK_PUBLIC_URL=https://api.example.com/twilio/inbounddotenvnpm install dotenv@16.4.5import "dotenv/config";/srv/app/.envTWILIO_ACCOUNT_SID=YOUR_ACCOUNT_SID
TWILIO_AUTH_TOKEN=0123456789abcdef0123456789abcdef
TWILIO_MESSAGING_SERVICE_SID=YOUR_MG_SID
TWILIO_STATUS_CALLBACK_URL=https://api.example.com/twilio/status
TWILIO_INBOUND_WEBHOOK_PUBLIC_URL=https://api.example.com/twilio/inbounddotenvnpm install dotenv@16.4.5import "dotenv/config";/etc/systemd/system/messaging-api.service[Unit]
Description=Messaging API
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=messaging
Group=messaging
WorkingDirectory=/srv/messaging-api
EnvironmentFile=/etc/messaging-api/env
ExecStart=/usr/bin/node /srv/messaging-api/dist/server.js
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/srv/messaging-api /var/log/messaging-api
AmbientCapabilities=
CapabilityBoundingSet=
[Install]
WantedBy=multi-user.target/etc/messaging-api/envsudo install -m 600 -o root -g root /dev/null /etc/messaging-api/env/etc/messaging-api/envTWILIO_ACCOUNT_SID=YOUR_ACCOUNT_SID
TWILIO_AUTH_TOKEN=0123456789abcdef0123456789abcdef
TWILIO_MESSAGING_SERVICE_SID=YOUR_MG_SID
TWILIO_STATUS_CALLBACK_URL=https://api.example.com/twilio/status
TWILIO_INBOUND_WEBHOOK_PUBLIC_URL=https://api.example.com/twilio/inbound
PORT=3000/etc/systemd/system/messaging-api.service[Unit]
Description=Messaging API
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=messaging
Group=messaging
WorkingDirectory=/srv/messaging-api
EnvironmentFile=/etc/messaging-api/env
ExecStart=/usr/bin/node /srv/messaging-api/dist/server.js
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/srv/messaging-api /var/log/messaging-api
AmbientCapabilities=
CapabilityBoundingSet=
[Install]
WantedBy=multi-user.target/etc/messaging-api/envsudo install -m 600 -o root -g root /dev/null /etc/messaging-api/env/etc/messaging-api/envTWILIO_ACCOUNT_SID=YOUR_ACCOUNT_SID
TWILIO_AUTH_TOKEN=0123456789abcdef0123456789abcdef
TWILIO_MESSAGING_SERVICE_SID=YOUR_MG_SID
TWILIO_STATUS_CALLBACK_URL=https://api.example.com/twilio/status
TWILIO_INBOUND_WEBHOOK_PUBLIC_URL=https://api.example.com/twilio/inbound
PORT=3000/etc/nginx/conf.d/messaging-api.confserver {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
location /twilio/ {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 10s;
}
}http://127.0.0.1/etc/nginx/conf.d/messaging-api.confserver {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
location /twilio/ {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 10s;
}
}http://127.0.0.1statusCallbackdeliveredundeliveredfailed// on /twilio/status
if (status === "undelivered" || status === "failed") {
await sqs.sendMessage({
QueueUrl: process.env.FALLBACK_QUEUE_URL,
MessageBody: JSON.stringify({ messageSid, to: req.body.To, reason: req.body.ErrorCode }),
});
}statusCallbackdeliveredundeliveredfailed// 在/twilio/status端点
if (status === "undelivered" || status === "failed") {
await sqs.sendMessage({
QueueUrl: process.env.FALLBACK_QUEUE_URL,
MessageBody: JSON.stringify({ messageSid, to: req.body.To, reason: req.body.ErrorCode }),
});
}From<Response>
<Message>Your ticket INC-2048 is open. Reply HELP for options.</Message>
</Response>From<Response>
<Message>Your ticket INC-2048 is open. Reply HELP for options.</Message>
</Response>HELPSTOPSTATUS <id>ONCALLACK <incident>HELPSTOPSTATUS <id>ONCALLACK <incident>idempotencyKeyidempotencyKey -> MessageSidMessageSididempotency_keyidempotencyKeyidempotencyKey -> MessageSidMessageSididempotency_keyToToTwilio could not find a Channel with the specified From addressThe 'To' number +1415555 is not a valid phone number.Twilio could not find a Channel with the specified From addressThe 'To' number +1415555 is not a valid phone number.AuthenticateUnable to create record: AuthenticateTWILIO_AUTH_TOKENAuthenticateUnable to create record: AuthenticateTWILIO_AUTH_TOKENToo Many RequestsToo Many RequestsThe message From/To pair violates a blacklist rule.The message From/To pair violates a blacklist rule.Unreachable destination handsetUnreachable destination handsetUnknown destination handsetUnknown destination handset'To' number is not a valid mobile number'To' number is not a valid mobile numberThe From phone number +14155552671 is not a valid, SMS-capable inbound phone number or short code for your account.The From phone number +14155552671 is not a valid, SMS-capable inbound phone number or short code for your account.Invalid signatureexpress.urlencoded()app.set('trust proxy', true)Invalid signatureexpress.urlencoded()app.set('trust proxy', true)statusCallbackstatusCallbackTWILIO_AUTH_TOKENTWILIO_AUTH_TOKENX-Twilio-SignatureX-Twilio-Signatureexpress.urlencoded({ limit: "64kb", extended: false })express.urlencoded({ limit: "64kb", extended: false })MessageSidToFromMessageSidToFromchmod 600chmod 600maxPricemaxPricemessages.createstatusCallbackbodymessages.createstatusCallbackbodyqueuedsendingsentdeliveredundeliveredfaileddeliveredundeliveredqueuedsendingsentdeliveredundeliveredfailedundelivereddelivereddeliveredToTo/srv/messaging-api/src/send.jsimport "dotenv/config";
import twilio from "twilio";
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
export async function sendDeployNotification({ to, buildId }) {
const msg = await client.messages.create({
messagingServiceSid: process.env.TWILIO_MESSAGING_SERVICE_SID,
to,
body: `Build ${buildId} deployed to prod. Reply HELP for support, STOP to opt out.`,
statusCallback: process.env.TWILIO_STATUS_CALLBACK_URL,
provideFeedback: true,
});
return { sid: msg.sid, status: msg.status };
}node -e 'import("./src/send.js").then(m=>m.sendDeployNotification({to:"+14155550123",buildId:"742"}).then(console.log))'/srv/messaging-api/src/send.jsimport "dotenv/config";
import twilio from "twilio";
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
export async function sendDeployNotification({ to, buildId }) {
const msg = await client.messages.create({
messagingServiceSid: process.env.TWILIO_MESSAGING_SERVICE_SID,
to,
body: `Build ${buildId} deployed to prod. Reply HELP for support, STOP to opt out.`,
statusCallback: process.env.TWILIO_STATUS_CALLBACK_URL,
provideFeedback: true,
});
return { sid: msg.sid, status: msg.status };
}node -e 'import("./src/send.js").then(m=>m.sendDeployNotification({to:"+14155550123",buildId:"742"}).then(console.log))'/srv/messaging-api/app.pyfrom fastapi import FastAPI, Request, Response
from twilio.request_validator import RequestValidator
from twilio.twiml.messaging_response import MessagingResponse
import os
app = FastAPI()
validator = RequestValidator(os.environ["TWILIO_AUTH_TOKEN"])
def create_ticket(from_number: str, body: str) -> str:
# Replace with real integration
return "INC-2048"
@app.post("/twilio/inbound")
async def inbound(request: Request):
form = await request.form()
signature = request.headers.get("X-Twilio-Signature", "")
url = os.environ["TWILIO_INBOUND_WEBHOOK_PUBLIC_URL"]
if not validator.validate(url, dict(form), signature):
return Response("Invalid signature", status_code=403)
from_number = form.get("From") or ""
body = (form.get("Body") or "").strip()
ticket = create_ticket(from_number, body)
resp = MessagingResponse()
resp.message(f"Created {ticket}. Reply HELP for options. Reply STOP to opt out.")
return Response(str(resp), media_type="text/xml")python3.11 -m venv .venv && source .venv/bin/activate
pip install fastapi==0.109.2 uvicorn==0.27.1 twilio==9.4.1 python-multipart==0.0.9
uvicorn app:app --host 0.0.0.0 --port 3000/srv/messaging-api/app.pyfrom fastapi import FastAPI, Request, Response
from twilio.request_validator import RequestValidator
from twilio.twiml.messaging_response import MessagingResponse
import os
app = FastAPI()
validator = RequestValidator(os.environ["TWILIO_AUTH_TOKEN"])
def create_ticket(from_number: str, body: str) -> str:
# 替换为实际集成逻辑
return "INC-2048"
@app.post("/twilio/inbound")
async def inbound(request: Request):
form = await request.form()
signature = request.headers.get("X-Twilio-Signature", "")
url = os.environ["TWILIO_INBOUND_WEBHOOK_PUBLIC_URL"]
if not validator.validate(url, dict(form), signature):
return Response("Invalid signature", status_code=403)
from_number = form.get("From") or ""
body = (form.get("Body") or "").strip()
ticket = create_ticket(from_number, body)
resp = MessagingResponse()
resp.message(f"Created {ticket}. Reply HELP for options. Reply STOP to opt out.")
return Response(str(resp), media_type="text/xml")python3.11 -m venv .venv && source .venv/bin/activate
pip install fastapi==0.109.2 uvicorn==0.27.1 twilio==9.4.1 python-multipart==0.0.9
uvicorn app:app --host 0.0.0.0 --port 3000/srv/messaging-api/src/status.jsimport express from "express";
import twilio from "twilio";
const app = express();
app.use(express.urlencoded({ extended: false, limit: "64kb" }));
app.post("/twilio/status", async (req, res) => {
const signature = req.header("X-Twilio-Signature") || "";
const url = process.env.TWILIO_STATUS_CALLBACK_PUBLIC_URL;
const ok = twilio.validateRequest(process.env.TWILIO_AUTH_TOKEN, signature, url, req.body);
if (!ok) return res.status(403).send("Invalid signature");
const { MessageSid, MessageStatus, ErrorCode, To } = req.body;
// Example: emit metric
console.log("twilio_status", { MessageSid, MessageStatus, ErrorCode });
if (MessageStatus === "failed" || MessageStatus === "undelivered") {
// enqueue fallback (pseudo)
console.log("enqueue_fallback", { to: To, messageSid: MessageSid, reason: ErrorCode });
}
res.status(204).send();
});
app.listen(3000);/srv/messaging-api/src/status.jsimport express from "express";
import twilio from "twilio";
const app = express();
app.use(express.urlencoded({ extended: false, limit: "64kb" }));
app.post("/twilio/status", async (req, res) => {
const signature = req.header("X-Twilio-Signature") || "";
const url = process.env.TWILIO_STATUS_CALLBACK_PUBLIC_URL;
const ok = twilio.validateRequest(process.env.TWILIO_AUTH_TOKEN, signature, url, req.body);
if (!ok) return res.status(403).send("Invalid signature");
const { MessageSid, MessageStatus, ErrorCode, To } = req.body;
// 示例:发送指标
console.log("twilio_status", { MessageSid, MessageStatus, ErrorCode });
if (MessageStatus === "failed" || MessageStatus === "undelivered") {
// 加入备用队列(伪代码)
console.log("enqueue_fallback", { to: To, messageSid: MessageSid, reason: ErrorCode });
}
res.status(204).send();
});
app.listen(3000);21614undeliveredmsg = client.messages.create(
messaging_service_sid=os.environ["TWILIO_MESSAGING_SERVICE_SID"],
to="+14155550123",
body="Incident screenshot attached.",
media_url=[presigned_url], # TTL >= 2h
status_callback="https://api.example.com/twilio/status",
)MMS failed on your carrier. View: https://app.example.com/incidents/INC-204821614undeliveredmsg = client.messages.create(
messaging_service_sid=os.environ["TWILIO_MESSAGING_SERVICE_SID"],
to="+14155550123",
body="Incident screenshot attached.",
media_url=[presigned_url], # 有效期≥2h
status_callback="https://api.example.com/twilio/status",
)MMS failed on your carrier. View: https://app.example.com/incidents/INC-2048import pLimit from "p-limit";
import twilio from "twilio";
const limit = pLimit(20);
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
async function sendOne(to) {
try {
return await client.messages.create({
messagingServiceSid: process.env.TWILIO_MESSAGING_SERVICE_SID,
to,
body: "Maintenance tonight 01:00-02:00 UTC. Reply STOP to opt out.",
statusCallback: process.env.TWILIO_STATUS_CALLBACK_URL,
});
} catch (e) {
const code = e?.code;
if (code === 21610) {
// mark opted out
return null;
}
throw e;
}
}
await Promise.all(recipients.map((to) => limit(() => sendOne(to))));npm install p-limit@5.0.0 twilio@4.23.0import pLimit from "p-limit";
import twilio from "twilio";
const limit = pLimit(20);
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
async function sendOne(to) {
try {
return await client.messages.create({
messagingServiceSid: process.env.TWILIO_MESSAGING_SERVICE_SID,
to,
body: "Maintenance tonight 01:00-02:00 UTC. Reply STOP to opt out.",
statusCallback: process.env.TWILIO_STATUS_CALLBACK_URL,
});
} catch (e) {
const code = e?.code;
if (code === 21610) {
// 标记为已退订
return null;
}
throw e;
}
}
await Promise.all(recipients.map((to) => limit(() => sendOne(to))));npm install p-limit@5.0.0 twilio@4.23.0| Task | Command / API | Key flags / fields |
|---|---|---|
| Send SMS (CLI) | | |
| Send MMS (CLI) | | |
| Fetch message | | |
| List messages | | |
| Create Messaging Service | | |
| Update service webhooks | | |
| Attach number to service | | |
| Inbound webhook | HTTP POST | Validate |
| Status callback | HTTP POST | |
| 任务 | 命令/API | 关键参数/字段 |
|---|---|---|
| 发送SMS(CLI) | | |
| 发送MMS(CLI) | | |
| 获取消息详情 | | |
| 列出消息 | | |
| 创建消息服务 | | |
| 更新服务Webhook | | |
| 关联号码到服务 | | |
| 入站Webhook | HTTP POST | 验证 |
| 状态回调 | HTTP POST | |
twiliotwiliotwilio-voicetwilio-verifysendgrid-emailobservabilitytwilio-voicetwilio-verifysendgrid-emailobservabilityaws-sns-smsmessagebird-smsvonage-smsfirebase-cloud-messagingaws-sns-smsmessagebird-smsvonage-smsfirebase-cloud-messaging