原文:How I Built an SMS Gateway with a $20 Android Phone Content was rephrased for compliance with licensing restrictions
为什么要自建短信网关
商业短信平台(国外的 Twilio、国内的阿里云短信/腾讯云短信)按条计费,通常 0.03–0.05 元/条。对于正式的 C 端业务(验证码、营销通知),这些平台是必选项——它们有运营商直连通道、模板审核、到达率保障。
但有一类场景,你只是想给自己或团队内部发几条通知,不需要那么"正规":
- 服务器挂了,给自己发条短信告警
- 定时任务跑完了,短信通知一下结果
- 智能家居传感器异常,短信提醒自己
- IoT 设备在偏远地区没有稳定网络,但有手机信号,用短信做数据回传
- 开发阶段调试短信流程,不想花钱接正规通道
这些场景的共同特点是:发送量小、接收者是自己人、不需要模板审核。
方案很简单:找一台闲置的 Android 手机,装上开源应用 SMS Gateway for Android,它会把手机变成一个带 REST API 的短信网关。短信走手机 SIM 卡的套餐,成本几乎为零。
工作原理
整个方案的本质是把 Android 手机的短信收发能力包装成 HTTP API:
发送短信(出站): 你的服务器 → HTTP POST → SMS Gateway App → Android 短信 API → 运营商网络 → 对方手机
接收短信(入站): 对方手机 → 运营商网络 → Android 收到短信 → SMS Gateway App 拦截 → HTTP Webhook → 你的服务器
SMS Gateway 做了两件事:
- 把 Android 的短信能力暴露为 REST API
- 通过 Webhook 把收到的短信实时推送给你的后端
适用场景与边界
适合用的场景
| 场景 | 说明 |
|---|---|
| 运维告警 | 服务宕机、磁盘满、证书过期,给自己发短信,比邮件通知更及时 |
| 定时任务通知 | 数据备份完成、报表生成完毕、爬虫跑完了 |
| 智能家居/IoT | 温湿度异常、门窗传感器触发、摄像头离线 |
| 开发测试 | 调试短信相关业务逻辑,不想花钱买正规短信通道 |
| 内网私密通知 | 本地模式下消息完全不经过第三方,数据不出局域网 |
| 网络降级备份 | 主网络断了,用短信作为备用通知通道 |
不适合的场景
| 场景 | 原因 |
|---|---|
| 给用户发验证码 | 到达率无保障,不合规,应使用阿里云/腾讯云短信 |
| 营销群发 | 运营商会封号,违反相关法规 |
| 大量发送(日均 50+ 条) | 运营商风控会触发,可能被限制或封号 |
| 面向 C 端用户的任何通知 | 应走正规短信平台,有模板审核和到达率保障 |
一句话总结:给自己人发少量通知——用它;给外部用户发业务短信——用正规平台。
前置条件
- 一台 Android 手机(5.0+),插有 SIM 卡(国内任意运营商均可)
- 一个后端应用(本文以 Next.js 15 + App Router 为例,任何后端框架都行)
- Node.js 18+
- 使用云模式时需要 ngrok 做内网穿透
安装 SMS Gateway
- 国内无法访问 Google Play,直接从 GitHub Releases 下载 APK 安装
- 打开应用,授予短信权限
- 主界面上有 Local Server 和 Cloud Server 两个开关
应用支持两种模式——本地模式和云模式,下面分别介绍。
本地服务器模式
本地模式直接在手机上运行一个 HTTP 服务器,后端通过局域网与之通信。不依赖云服务,不经过第三方服务器,是最简单也最私密的方案。对于国内用户,推荐优先使用本地模式,避免数据经过境外服务器。
配置步骤
- 打开 "Local Server" 开关
- 进入 Settings > Local Server 配置:
- 端口:1024–65535(默认 8080)
- 用户名:至少 3 个字符
- 密码:至少 8 个字符
- 点击 "Offline",状态变为 "Online"
- 记下显示的本地 IP 地址(例如
192.168.1.50)
验证服务是否正常:
# 健康检查
curl http://192.168.1.50:8080/health
# Swagger 文档
open http://192.168.1.50:8080/docs
发送第一条短信
curl -X POST http://192.168.1.50:8080/message \
-u "admin:yourpassword" \
-H "Content-Type: application/json" \
-d '{
"textMessage": { "text": "服务器磁盘使用率超过90%,请及时处理" },
"phoneNumbers": ["+8613800138000"]
}'
手机会用自己的号码发送短信,费用按你的手机套餐计算。
注册 Webhook 接收入站短信
curl -X POST http://192.168.1.50:8080/webhooks \
-u "admin:yourpassword" \
-H "Content-Type: application/json" \
-d '{
"id": "my-webhook",
"url": "http://192.168.1.100:4000/api/sms/webhook",
"event": "sms:received"
}'
把 192.168.1.100 替换成你开发机的局域网 IP。两台设备需要在同一个 WiFi 网络下。
本地模式注意事项
- AP 隔离:很多路由器(尤其是 Mesh 网络和办公 WiFi)会阻止设备间通信。如果连不上手机,检查路由器设置中的 "AP 隔离" 或 "客户端隔离" 并关闭它。
- 电池优化:Android 会为了省电杀掉后台服务。在手机设置中为 SMS Gateway 关闭电池优化。dontkillmyapp.com 有各品牌手机的具体操作指南,国产手机(小米、华为、OPPO 等)的后台管理尤其激进,务必仔细设置。
- 保持充电:手机应该一直插着充电器,这是它唯一的工作。
云服务器模式
云模式设置更简单,且不受局域网限制。手机连接到 SMS Gateway 的云中继服务(api.sms-gate.app),后端也通过同一个云 API 通信。
国内用户注意:云模式的中继服务器在境外,短信内容会经过第三方服务器。如果你在意数据隐私,建议使用本地模式。另外,国内网络访问境外 API 可能不稳定,需要考虑连通性问题。
启用步骤
- 在应用中打开 "Cloud Server" 开关
- 点击 "Offline",自动连接并注册
- 系统会自动生成用户名和密码(在 Cloud Server 区域可见)
- 记下这些凭据,API 调用时需要用到
云模式采用混合推送架构:Firebase Cloud Messaging 作为主通道,Server-Sent Events 作为备用,15 分钟轮询作为最后兜底。
通过云 API 发送短信
curl -X POST https://api.sms-gate.app/3rdparty/v1/messages \
-u "YOUR_USERNAME:YOUR_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"textMessage": { "text": "来自云端的问候!" },
"phoneNumbers": ["+8613800138000"]
}'
注册 Webhook(云模式)
云模式下 Webhook URL 必须是 HTTPS。本地开发时可以用 ngrok:
# 启动 ngrok 隧道
ngrok http 4000
# 输出: https://abc123.ngrok.app
# 注册 Webhook
curl -X POST https://api.sms-gate.app/3rdparty/v1/webhooks \
-u "YOUR_USERNAME:YOUR_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"url": "https://abc123.ngrok.app/api/sms/webhook",
"event": "sms:received"
}'
管理 Webhook
# 列出所有 Webhook
curl -u "YOUR_USERNAME:YOUR_PASSWORD" \
https://api.sms-gate.app/3rdparty/v1/webhooks
# 删除 Webhook
curl -X DELETE -u "YOUR_USERNAME:YOUR_PASSWORD" \
https://api.sms-gate.app/3rdparty/v1/webhooks/WEBHOOK_ID
代码实现——Next.js 集成
核心思路是做一个 Provider 抽象层,切换短信通道时不需要改动业务逻辑。将来如果要换成阿里云短信,只需新增一个 Provider 实现即可。
Provider 接口
// src/lib/sms/provider.ts
export interface InboundSms {
from: string;
body: string;
receivedAt?: Date;
}
export interface SmsProvider {
send(to: string, body: string): Promise<string>;
parseWebhook(req: Request): Promise<InboundSms | null>;
webhookResponse(replyText?: string): Response;
}
export async function getSmsProvider(): Promise<SmsProvider> {
const provider = process.env.SMS_PROVIDER || "sms-gate";
switch (provider) {
case "sms-gate": {
const { SmsGateProvider } = await import("./sms-gate");
return new SmsGateProvider();
}
case "console": {
const { ConsoleProvider } = await import("./console");
return new ConsoleProvider();
}
// 将来可以扩展:
// case "aliyun": { ... }
// case "tencent": { ... }
default:
throw new Error(`Unknown SMS provider: ${provider}`);
}
}
SMS Gate Provider
同时处理本地和云 API 的差异:
// src/lib/sms/sms-gate.ts
import type { SmsProvider, InboundSms } from "./provider";
const SMSGATE_URL = process.env.SMSGATE_URL || "http://localhost:8080";
const SMSGATE_USER = process.env.SMSGATE_USER || "";
const SMSGATE_PASSWORD = process.env.SMSGATE_PASSWORD || "";
export class SmsGateProvider implements SmsProvider {
private headers(): Record<string, string> {
const auth = Buffer.from(
`${SMSGATE_USER}:${SMSGATE_PASSWORD}`
).toString("base64");
return {
"Content-Type": "application/json",
Authorization: `Basic ${auth}`,
};
}
async send(to: string, body: string): Promise<string> {
const isCloud = SMSGATE_URL.includes("api.sms-gate.app");
const endpoint = isCloud
? `${SMSGATE_URL}/3rdparty/v1/messages`
: `${SMSGATE_URL}/api/3rdparty/v1/message`;
const payload = isCloud
? { textMessage: { text: body }, phoneNumbers: [to] }
: { phoneNumbers: [to], message: body };
const res = await fetch(endpoint, {
method: "POST",
headers: this.headers(),
body: JSON.stringify(payload),
});
if (!res.ok) {
const err = await res.text();
throw new Error(`SMS Gate send failed: ${res.status} ${err}`);
}
const data = await res.json();
return data.id || "sent";
}
async parseWebhook(req: Request): Promise<InboundSms | null> {
try {
const body = await req.json();
if (body.event !== "sms:received" || !body.payload) {
return null;
}
const { phoneNumber, message, receivedAt } = body.payload;
if (!phoneNumber || !message) return null;
return {
from: phoneNumber,
body: message,
receivedAt: receivedAt ? new Date(receivedAt) : new Date(),
};
} catch {
return null;
}
}
webhookResponse(): Response {
return new Response(JSON.stringify({ ok: true }), {
headers: { "Content-Type": "application/json" },
});
}
}
Webhook 路由
一个运维告警场景的 Webhook 处理器示例:
// src/app/api/sms/webhook/route.ts
import { NextRequest } from "next/server";
import { getSmsProvider } from "@/lib/sms/provider";
export async function POST(req: NextRequest) {
const provider = await getSmsProvider();
const sms = await provider.parseWebhook(req);
if (!sms) {
return new Response("Bad request", { status: 400 });
}
const { from, body } = sms;
// 示例:通过短信回复查询服务器状态
if (body.trim().toLowerCase() === "status") {
await provider.send(from, "CPU: 23% | 内存: 67% | 磁盘: 45% | 全部正常");
return provider.webhookResponse();
}
console.log(`[SMS from ${from}]: ${body}`);
await provider.send(from, "收到,已记录。");
return provider.webhookResponse();
}
Console Provider(用于测试)
本地开发时不需要手机的替代方案:
// src/lib/sms/console.ts
import type { SmsProvider, InboundSms } from "./provider";
export class ConsoleProvider implements SmsProvider {
async send(to: string, body: string): Promise<string> {
console.log(`[SMS -> ${to}] ${body}`);
return `console-${Date.now()}`;
}
async parseWebhook(req: Request): Promise<InboundSms | null> {
const data = await req.json();
return {
from: data.from || "+8613800138000",
body: data.body || "",
receivedAt: new Date(),
};
}
webhookResponse(): Response {
return new Response(JSON.stringify({ ok: true }), {
headers: { "Content-Type": "application/json" },
});
}
}
环境变量
# .env
# Provider: "sms-gate" | "console"
SMS_PROVIDER=sms-gate
# 本地模式(推荐国内用户使用)
SMSGATE_URL=http://192.168.1.50:8080
SMSGATE_USER=admin
SMSGATE_PASSWORD=yourpassword
# 云模式(境外中继,国内网络可能不稳定)
# SMSGATE_URL=https://api.sms-gate.app
# SMSGATE_USER=auto-generated-username
# SMSGATE_PASSWORD=auto-generated-password
实战示例:服务器告警通知
这是一个最典型的使用场景——当服务器出现异常时,通过短信网关发送告警:
// src/lib/alert.ts
import { getSmsProvider } from "@/lib/sms/provider";
const ADMIN_PHONES = (process.env.ADMIN_PHONES || "").split(",").filter(Boolean);
export async function sendAlert(message: string) {
if (ADMIN_PHONES.length === 0) {
console.warn("未配置告警手机号,跳过短信通知");
return;
}
const provider = await getSmsProvider();
for (const phone of ADMIN_PHONES) {
try {
await provider.send(phone.trim(), `[告警] ${message}`);
} catch (err) {
console.error(`发送告警短信失败 (${phone}):`, err);
}
}
}
// 使用示例:
// await sendAlert("数据库连接池耗尽,当前活跃连接数: 100/100");
// await sendAlert("SSL 证书将在 3 天后过期");
// await sendAlert("磁盘使用率 95%,请及时清理");
# .env 中配置告警接收人
ADMIN_PHONES=+8613800138000,+8613900139000
Webhook 载荷参考
当有人给你的 Android 手机发短信时,SMS Gateway 会向你的 Webhook URL 发送 POST 请求:
{
"id": "Ey6ECgOkVVFjz3CL48B8C",
"webhookId": "LreFUt-Z3sSq0JufY9uWB",
"deviceId": "your-device-id",
"event": "sms:received",
"payload": {
"messageId": "abc123",
"message": "你好!",
"sender": "+8613800138000",
"recipient": "+8613900139000",
"simNumber": 1,
"receivedAt": "2026-04-01T12:41:59.000+00:00"
}
}
可用事件
| 事件 | 说明 |
|---|---|
sms:received | 收到入站短信 |
sms:sent | 出站短信已发送 |
sms:delivered | 出站短信已确认送达 |
sms:failed | 出站短信发送失败 |
system:ping | 心跳——设备仍在线 |
Webhook 安全
SMS Gateway 使用 HMAC-SHA256 对 Webhook 载荷进行签名,包含两个 Header:
X-Signature:十六进制编码的 HMAC-SHA256 签名X-Timestamp:用于签名的 Unix 时间戳
import crypto from "crypto";
function verifyWebhook(
signingKey: string,
payload: string,
timestamp: string,
signature: string
): boolean {
const expected = crypto
.createHmac("sha256", signingKey)
.update(payload + timestamp)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(signature, "hex")
);
}
重试机制
如果你的服务器在 30 秒内没有返回 2xx 响应,SMS Gateway 会以指数退避策略重试——从 10 秒开始,每次翻倍,最多重试 14 次(约 2 天)。默认行为已经很合理,不需要额外配置。
测试完整流程
1. 启动开发服务器
npm run dev
# Next.js 运行在 http://localhost:4000
2. 暴露服务(云模式才需要)
ngrok http 4000
# https://abc123.ngrok.app -> http://localhost:4000
3. 注册 Webhook
# 本地模式
curl -X POST http://192.168.1.50:8080/webhooks \
-u "admin:yourpassword" \
-H "Content-Type: application/json" \
-d '{
"id": "alert-webhook",
"url": "http://192.168.1.100:4000/api/sms/webhook",
"event": "sms:received"
}'
4. 发一条短信试试
用另一台手机给你的 Android 手机发短信,你会看到:
- SMS Gateway 收到短信
- Webhook 触发,请求发到你的服务器
- 服务器处理请求
- 通过 API 发送回复短信
- 发送者的手机收到回复
不用手机也能测试
# 用 console provider 模拟
SMS_PROVIDER=console npm run dev
curl -X POST http://localhost:4000/api/sms/webhook \
-H "Content-Type: application/json" \
-d '{"from": "+8613800138000", "body": "status"}'
生产环境部署
手机设置
- 专用设备:用一台闲置的 Android 手机,配一张最便宜的套餐 SIM 卡。它的一生就是插着充电器连着 WiFi。
- 关闭电池优化:为 SMS Gateway 关闭电池优化。国产手机(小米、华为、OPPO、vivo)的后台管理特别激进,需要在"电池"和"自启动管理"两个地方都设置。去 dontkillmyapp.com 查你的设备型号。
- 开机自启:在 SMS Gateway 应用设置中启用 "start on boot"。
- 监控:注册一个
system:pingWebhook,设备离线时发出告警(可以用另一个通道,比如邮件或企业微信机器人)。
本地模式 vs 云模式
| 本地模式 | 云模式 | |
|---|---|---|
| 延迟 | 更低(直连) | 略高(经过中继) |
| 网络 | 需要同一网络 | 随处可用 |
| 隐私 | 消息不离开你的网络 | 消息经过境外服务器 |
| 可靠性 | 取决于你的网络 | FCM/SSE 冗余(国内可能受限) |
| 国内推荐度 | ⭐⭐⭐⭐⭐ | ⭐⭐ |
国内用户强烈建议使用本地模式。 如果你的服务器不在本地局域网,可以通过 VPN 或内网穿透(frp、ngrok)打通网络,仍然比走境外云中继更可控。
成本对比(国内视角)
| 方案 | 单条成本 | 每月费用(500 条) | 适用场景 |
|---|---|---|---|
| 阿里云/腾讯云短信 | ~0.04 元/条 | ~20 元 | C 端业务、验证码、正式通知 |
| 手机网关 + 低价套餐 | ~0 元/条 | 8–19 元(套餐费) | 内部告警、个人通知、开发测试 |
国内使用的注意事项
- 发送频率控制:不要短时间内大量发送,建议日均不超过 30–50 条,避免触发运营商风控
- 内容合规:即使是给自己发,也不要发送违规内容,运营商有内容审查机制
- 号码选择:建议用一张专门的副卡,不要用主力号码,万一被限制不影响日常使用
- 双通道备份:重要告警建议同时配置企业微信/钉钉机器人作为备用通道,短信网关作为最后兜底
- APK 安装:国内无法访问 Google Play,从 GitHub Releases 下载 APK 即可
更多玩法:短信网关还能做什么
前面的例子集中在运维告警和定时通知,但"手机短信能力 + HTTP API"这个组合的想象空间远不止于此。
双向交互:短信 ChatOps
短信不只是单向通知,入站短信 + Webhook 让它变成一个双向交互通道:
- 短信执行命令:发"重启 nginx"触发服务器操作,发"查余额"返回账户信息。相当于一个不依赖任何 App 的极简 ChatOps。
- 短信审批流:关键操作(数据库删除、生产部署)需要管理员回复"确认"才执行。短信作为带外确认通道,比邮件审批更即时。
- 短信触发 CI/CD:紧急情况下(只有手机没有电脑),发一条"deploy prod v2.3.1"触发生产部署。
认证与安全
- 自建 2FA / OTP:给内部系统(VPN、后台管理面板)做短信验证码,不用接商业短信平台。适合小团队内部工具。
- 登录告警:有人登录服务器或后台时,短信通知 IP 和时间,异常登录第一时间知道。
- 蜜罐触发通知:服务器上部署蜜罐文件或端口,被访问立刻短信告警,作为入侵检测的最后一道通知。
IoT 与边缘场景
- 远程控制:在没有稳定网络的偏远地区(农场、工地、山区基站),通过短信发指令控制设备开关、调整参数。手机信号覆盖远比 WiFi 广。
- 短信数据采集:传感器数据通过短信回传到网关手机,再由 Webhook 写入数据库。适合低频数据上报(每小时一次温湿度之类的)。
- 车辆/资产追踪:在车上放一台带 GPS 的手机,定时短信上报位置,或者发短信查询当前位置。
网络降级与灾备
- 断网应急通道:主网络挂了,短信作为唯一的通信通道。通过短信触发服务器重启、切换 DNS、启动备用线路。
- 灾难恢复协调:团队成员在网络中断时通过短信网关互相通信、协调恢复操作。
自动化与工作流
- 表单/工单通知:内部工单系统有新工单时短信通知值班人员,比 App 推送更不容易被忽略。
- 定时提醒:吃药、会议、账单到期,短信提醒比 App 通知更可靠。
- 完全离线的通知系统:本地模式下消息不经过任何第三方服务器,对数据主权有要求的场景(医疗、金融内部系统)是一个优势。
这些玩法的共同模式是:短信作为一个不依赖互联网的可编程通信通道。网络可能断、App 可能被杀、邮件可能被忽略,但短信几乎总能送达。
不止短信:旧手机还能做什么
短信网关利用的是手机的 SIM 卡和短信能力,但一台旧 Android 手机的硬件资源远不止于此。同样的思路——把硬件能力包装成可编程的 API——可以套到手机的其他硬件上。
摄像头 → 安防监控
IP Webcam 把手机摄像头变成 RTSP/HTTP 视频流,直接对接 Home Assistant 或 NVR。配合运动检测,触发时通过短信网关发告警——一台手机同时干两件事。
GPS → 追踪定位
Traccar Client 是开源的 GPS 追踪方案,客户端和服务端都开源。把旧手机放在车里或贵重物品里,定时上报 GPS 坐标到你的服务器。比买专门的 GPS 追踪器便宜,而且手机有 SIM 卡可以走数据网络上报。
麦克风 → 环境监测
噪音监测(检测分贝超标告警,适合机房、工厂)、婴儿监控(检测到哭声推送通知)、甚至配合语音识别做本地语音控制。
屏幕 → 信息仪表盘
全屏显示 Grafana 面板、Home Assistant 界面、天气、日历,或者做小店铺的数字标牌、会议室门口的预约状态。用 Fully Kiosk Browser 锁定成单一用途的显示终端。
蓝牙 → BLE 网关
采集周围的蓝牙温湿度传感器(比如小米的那些)数据,转发到 MQTT 或 HTTP 后端。也可以做蓝牙 Beacon 检测,实现简易的人员到离感知。
WiFi → 网络探针
定时 ping 关键服务和设备,断了就通过短信网关告警。一台手机同时当网络探针和告警通道。
NFC → 门禁打卡
旧手机固定在门口,刷 NFC 标签记录出入,或者做资产盘点——扫 NFC 标签登记设备信息。
计算能力 → 轻量级服务器
通过 Termux 装 Python、Node.js,跑定时脚本、小型爬虫。挂个 SD 卡跑 aria2 做下载节点。
组合才是最有价值的
一台旧手机同时跑短信网关 + 摄像头监控 + 环境传感 + 网络探针,就是一个全能的"哨兵节点"——检测到异常用摄像头拍一张照,同时短信通知你。所有这些能力都可以用同一套"硬件能力 → HTTP API"的架构模式来统一管理。
供电与续航:绕不开的现实问题
旧手机再利用,供电是必须面对的问题。不同场景差异很大。
固定场景:插电就行
短信网关、信息仪表盘、BLE 网关、NAS 下载节点这些,手机放在家里或办公室,一直插着充电器就行。需要注意的是长期满电充电会加速电池老化膨胀:
- 部分手机支持"充电保护",限制充电到 80%(三星、索尼等有此功能)
- 没有此功能的手机,可以用智能插座定时断电,做简易的充放循环
- 定期检查电池是否鼓包,发现膨胀立即停用
移动场景:需要取舍
GPS 追踪(放车里)、户外摄像头、偏远地区 IoT 节点,没有固定电源,供电方案:
| 方案 | 续航 | 适用场景 |
|---|---|---|
| 车载 USB 充电器 | 行驶时无限,熄火后 1–2 天 | 车辆追踪 |
| 太阳能板(5–10W)+ 充电宝 | 持续供电(晴天) | 户外监控、农场、工地 |
| 大容量充电宝(20000mAh) | 3–5 天(低功耗模式) | 临时部署、短期监测 |
软件省电策略
硬件供电之外,软件层面的优化同样关键:
- 关屏幕:屏幕是最耗电的硬件,不需要显示的场景保持息屏
- 降低采集频率:GPS 不需要秒级上报,每 5 分钟一次够用
- 按需唤醒:平时休眠,收到短信指令或定时器触发时才工作
- 关闭不用的硬件:不用蓝牙就关蓝牙,不用 WiFi 就走移动数据
- 自动化省电:Tasker 或 MacroDroid 可以设置规则,比如电量低于 20% 自动降低上报频率
电池安全:认真对待
旧手机长期充电或在高温环境运行,电池膨胀甚至起火的风险是真实存在的:
- 放在通风的地方,避免阳光直射和密闭空间
- 车内场景夏天温度能到 60–70°C,锂电池的安全上限一般是 45°C,这个要认真对待
- 如果手机支持,拆掉电池直接用 USB 供电(部分手机可以)
- 定期检查电池是否鼓包,发现异常立即停用
- 不要在床上、沙发上等易燃物旁边长期充电
总结
这个方案的起点是一个简单的需求:用闲置手机搭一个私有短信网关。但往深了想,它代表的是一种更通用的思路——把旧手机的硬件能力(短信、摄像头、GPS、蓝牙、麦克风、屏幕)包装成可编程的 API,让它变成一个低成本的边缘计算节点。
短信网关是其中最实用的一个切入点。短信的独特优势在于不依赖互联网——手机没网也能收短信,App 通知可能被关掉但短信不会。当你的服务器凌晨三点挂了,一条短信比一封邮件更可能把你叫醒。
对于正式的业务短信,老老实实用阿里云或腾讯云短信服务。对于给自己和团队发告警通知,一台闲置手机就够了。
这类工具已经形成了一个小品类,httpSMS 和 textbee 也做类似的事。SMS Gateway for Android 的优势在于本地模式好用、文档扎实、维护活跃(2026 年 3 月发布了 v1.56.0)。代码里的 Provider 抽象层也留好了扩展口——将来如果需要接入阿里云短信,加一个 AliyunProvider 就行。
而那台闲置手机,远不止能发短信。