相比自建短信网关或使用 Twilio 等海外平台,SUBMAIL 对中国企业开发者有明显优势:
sms/unionsend单接口自动按国家路由1. 注册账号并创建应用
前往 mysubmail.com 注册,进入控制台「应用集成」→「国际短信」,创建应用,获取:
AppID:应用唯一标识AppKey:应用密钥(请妥善保管,切勿提交至代码仓库)2. 确认账户余额
国际短信按条计费,发送前请在控制台确认余额充足。
JDK 版本要求:JDK 1.8 及以上
使用 OkHttp3 作为 HTTP 客户端,Gson 解析 JSON 响应。
Maven 依赖(pom.xml):
<dependencies>
<!-- HTTP 客户端 -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<!-- JSON 解析 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>Gradle 依赖(build.gradle):
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.google.code.gson:gson:2.10.1'https://api-v4.mysubmail.com/internationalsms/sendapplication/x-www-form-urlencoded主要请求参数:
appid(必需):国际短信应用 AppIDsignature(必需):normal 模式填 AppKey 明文;md5 模式填签名字符串to(必需):收件人手机号,必须携带国际区号,格式如 +1xxxxxxxxxx(美国)、+44xxxxxxxxxx(英国)、+81xxxxxxxxxx(日本)content(必需):短信正文sign_type(可选):认证类型,normal(测试用)或 md5(生产推荐)import okhttp3.*;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.IOException;
public class SubmailSmsSender {
// 从环境变量读取,避免硬编码密钥
private static final String APPID = System.getenv("SUBMAIL_APPID");
private static final String APPKEY = System.getenv("SUBMAIL_APPKEY");
private static final String API_URL =
"https://api-v4.mysubmail.com/internationalsms/send";
private final OkHttpClient client = new OkHttpClient();
/**
* 发送国际短信(normal 明文认证模式)
*
* @param to 收件人号码,需带国际区号,如 +1xxxxxxxxxx
* @param content 短信正文
* @return API 响应的 JsonObject
*/
public JsonObject sendSms(String to, String content) throws IOException {
// 构建请求体
RequestBody body = new FormBody.Builder()
.add("appid", APPID)
.add("signature", APPKEY) // normal 模式直接传 AppKey
.add("to", to)
.add("content", content)
.add("sign_type", "normal")
.build();
Request request = new Request.Builder()
.url(API_URL)
.post(body)
.build();
// 发送请求并解析响应
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("HTTP 请求失败,状态码:" + response.code());
}
String responseBody = response.body().string();
return JsonParser.parseString(responseBody).getAsJsonObject();
}
}
/**
* 统一处理 API 响应
*/
public static void handleResponse(JsonObject result) {
String status = result.get("status").getAsString();
if ("success".equals(status)) {
System.out.println("✅ 发送成功!");
System.out.println(" send_id: " + result.get("send_id").getAsString());
System.out.println(" 消耗额度: " + result.get("fee").getAsInt());
System.out.println(" 剩余额度: " + result.get("sms_credits").getAsInt());
} else {
System.out.println("❌ 发送失败!");
System.out.println(" 错误码: " + result.get("code").getAsString());
System.out.println(" 原因: " + result.get("msg").getAsString());
}
}
public static void main(String[] args) throws IOException {
SubmailSmsSender sender = new SubmailSmsSender();
// 发送给美国号码
JsonObject result = sender.sendSms(
"+11234567890",
"Your verification code is 8866. Valid for 5 minutes. --SUBMAIL"
);
handleResponse(result);
}
}import okhttp3.*;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.TreeMap;
public class SubmailSmsMd5Sender {
private static final String APPID = System.getenv("SUBMAIL_APPID");
private static final String APPKEY = System.getenv("SUBMAIL_APPKEY");
private static final String API_URL =
"https://api-v4.mysubmail.com/internationalsms/send";
private final OkHttpClient client = new OkHttpClient();
/**
* 构建 SUBMAIL MD5 数字签名
* 规则:appid + appkey + 排序后参数字符串 + appid + appkey,取 MD5
*/
private String buildMd5Signature(Map<String, String> params) {
// 使用 TreeMap 自动按 key 排序
TreeMap<String, String> sorted = new TreeMap<>(params);
sorted.remove("sign_type");
StringBuilder paramStr = new StringBuilder();
for (Map.Entry<String, String> entry : sorted.entrySet()) {
if (paramStr.length() > 0) paramStr.append("&");
paramStr.append(entry.getKey()).append("=").append(entry.getValue());
}
// 拼接签名原文
String raw = APPID + APPKEY + paramStr + APPID + APPKEY;
// MD5 加密
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hash = md.digest(raw.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte b : hash) {
sb.append(String.format("%02x", b));
}
return sb.toString();
} catch (Exception e) {
throw new RuntimeException("MD5 签名生成失败:" + e.getMessage());
}
}
/**
* 发送国际短信(MD5 签名认证模式)
*/
public JsonObject sendSmsWithMd5(String to, String content) throws IOException {
// 先组装参数(不含 sign_type 和 signature)
Map<String, String> params = new TreeMap<>();
params.put("appid", APPID);
params.put("to", to);
params.put("content", content);
// 生成 MD5 签名
String signature = buildMd5Signature(params);
// 构建最终请求体
FormBody.Builder formBuilder = new FormBody.Builder();
for (Map.Entry<String, String> entry : params.entrySet()) {
formBuilder.add(entry.getKey(), entry.getValue());
}
formBuilder.add("signature", signature);
formBuilder.add("sign_type", "md5");
Request request = new Request.Builder()
.url(API_URL)
.post(formBuilder.build())
.build();
try (Response response = client.newCall(request).execute()) {
String responseBody = response.body().string();
return JsonParser.parseString(responseBody).getAsJsonObject();
}
}
public static void main(String[] args) throws IOException {
SubmailSmsMd5Sender sender = new SubmailSmsMd5Sender();
// 发送给英国号码
JsonObject result = sender.sendSmsWithMd5(
"+447911123456",
"Hello! Your order #ORD20260323 has been shipped. --SUBMAIL"
);
System.out.println("API 响应:" + result.toString());
}
}如果你在 SUBMAIL 控制台预先创建了短信模板,可以使用 xsend 接口,通过变量动态替换模板内容,适合 OTP 验证码、订单通知等标准化场景:
import okhttp3.*;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.IOException;
import java.util.Map;
public class SubmailSmsTemplateSender {
private static final String APPID = System.getenv("SUBMAIL_APPID");
private static final String APPKEY = System.getenv("SUBMAIL_APPKEY");
private static final String XSEND_URL =
"https://api-v4.mysubmail.com/internationalsms/xsend";
private final OkHttpClient client = new OkHttpClient();
private final Gson gson = new Gson();
/**
* 使用模板发送国际短信
*
* @param to 收件人号码(带国际区号)
* @param project 模板 ID(控制台中的 project 字段)
* @param variables 模板变量,如 {"code": "8866", "minutes": "5"}
*/
public JsonObject sendWithTemplate(String to, String project,
Map<String, String> variables)
throws IOException {
// 将变量 Map 序列化为 JSON 字符串
String varsJson = gson.toJson(variables);
RequestBody body = new FormBody.Builder()
.add("appid", APPID)
.add("signature", APPKEY)
.add("to", to)
.add("project", project)
.add("vars", varsJson)
.add("sign_type", "normal")
.build();
Request request = new Request.Builder()
.url(XSEND_URL)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
String responseBody = response.body().string();
return JsonParser.parseString(responseBody).getAsJsonObject();
}
}
public static void main(String[] args) throws IOException {
SubmailSmsTemplateSender sender = new SubmailSmsTemplateSender();
// 模板内容示例:【SUBMAIL】你好 @var(name),验证码 @var(code),@var(minutes) 分钟有效
Map<String, String> vars = Map.of(
"name", "张三",
"code", "9527",
"minutes", "5"
);
JsonObject result = sender.sendWithTemplate(
"+8613812345678", // 中国大陆号码
"your_template_id",
vars
);
System.out.println("API 响应:" + result);
}
}需要向多个用户批量发送时,推荐以下方案:
使用线程池实现异步并发发送:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.List;
public class BulkSmsSender {
// 线程池大小根据 SUBMAIL 套餐 QPS 限制调整
private static final int THREAD_POOL_SIZE = 10;
public void sendBulk(List<String> phoneNumbers, String content) {
ExecutorService executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
SubmailSmsSender sender = new SubmailSmsSender();
for (String phone : phoneNumbers) {
executor.submit(() -> {
try {
var result = sender.sendSms(phone, content);
SubmailSmsSender.handleResponse(result);
} catch (Exception e) {
System.err.println("❌ 发送失败 [" + phone + "]:" + e.getMessage());
}
});
}
executor.shutdown();
}
}或使用 internationalsms/batchsend 接口,支持单次请求发送最多 10,000 个号码,性能更佳,建议超过 100 条时优先使用。
成功响应示例:
{
"status": "success",
"send_id": "abcdef1234567890",
"fee": 1,
"sms_credits": 99
}失败响应示例:
{
"status": "error",
"code": "103",
"msg": "Unauthorized"
}常见错误码:
103:AppID 或 AppKey 错误 → 检查环境变量配置是否正确104:签名验证失败 → 检查 MD5 签名算法,注意参数排序和编码格式(UTF-8)108:短信额度不足 → 前往控制台充值401:收件人号码格式错误 → 国际号码必须以 + 开头加区号402:内容违规 → 检查短信正文是否含敏感词🔐 绝对不要将 AppKey 硬编码在代码或 Git 仓库中,推荐以下方案:
Linux / macOS 环境变量:
export SUBMAIL_APPID="your_appid"
export SUBMAIL_APPKEY="your_appkey"Spring Boot 项目(application.yml):
submail:
appid: ${SUBMAIL_APPID}
appkey: ${SUBMAIL_APPKEY}@Value("${submail.appid}")
private String appId;
@Value("${submail.appkey}")
private String appKey;生产环境推荐方案:
.env 文件,并将其加入 .gitignorehttps://api-v4.mysubmail.com/internationalsms/sendhttps://api-v4.mysubmail.com/internationalsms/xsend+ 国际区号normal,生产用 md5与 Python 版本相比,Java 接入 SUBMAIL SMS API 同样简单直接,借助 OkHttp3 和 Gson,整个核心发送逻辑不超过 50 行代码。从注册到发出第一条国际短信,通常只需 30 分钟。
官方 Java SDK 参考:github.com
官方 API 文档:en.mysubmail.com
了解更多:
微信公众号
赛邮微博