PAYATHON 2026

未启用 billing 时能否使用 Mail API 免费配额?

数据阿明

结论

可以。只要使用 Google App Engine Mail API 的免费配额,通常不需要先启用 billing。

这次异常并不表示免费配额不可用。代码实际上是通过 Socket API 连接 SMTP 服务器,而未启用 billing 的应用无法使用 Socket API,所以抛出了以下异常:

ApiProxy$FeatureNotEnabledException:
The Socket API will be enabled for this application once billing has been enabled

正常使用 App Engine 内置 Mail 服务时,Transport.send(msg) 不会建立普通的 SMTP Socket 连接。

原因

代码写法本身符合 App Engine Java Mail API 的常见用法。问题很可能出在项目额外打包了 JavaMail 实现库,例如:

mail.jar
javax.mail.jar
jakarta.mail.jar

如果这些库位于 WEB-INF/lib,或者被 Maven、Gradle 依赖打进应用,就可能覆盖 App Engine 运行环境提供的 JavaMail 实现。这样一来,Transport.send(msg) 会按标准 JavaMail 的方式查找 SMTP 服务并建立网络连接,最终触发 Socket API 限制。

还要检查项目是否设置过类似参数:

props.put("mail.smtp.host", "smtp.gmail.com");

一旦配置了 SMTP 主机,发送过程也会使用 Socket,而不会调用 App Engine Mail API。

解决步骤

1. 删除应用自带的 JavaMail 实现

检查以下目录:

WEB-INF/lib/

删除自行添加的 JavaMail、SMTP 或 Jakarta Mail 实现包。如果项目使用 Maven 或 Gradle,也要检查依赖树,确认这些库没有通过间接依赖进入最终部署产物。

不要为了方便编译而把完整的 JavaMail 实现库部署到 App Engine。应使用与目标 App Engine 运行环境匹配的 SDK/API 依赖,并按照运行环境的要求,将由平台提供的运行时库标记为非打包依赖。

2. 不要设置外部 SMTP 参数

使用 App Engine Mail API 时,传入空的 Properties 即可:

Properties props = new Properties();
Session session = Session.getDefaultInstance(props, null);

不要配置 mail.smtp.host、SMTP 用户名、密码或端口。

3. 使用允许的发件人地址

发件人地址不能随意填写。对于经典 App Engine Mail API,通常可以使用应用管理员地址、当前登录用户的地址,或者平台允许的应用域地址。具体限制可能因 App Engine 运行环境和项目版本而不同,应以当前运行时的官方规则为准。

例如:

msg.setFrom(new InternetAddress("authorized-sender@example.com"));

admin-gmail-email-address 必须替换为真实且已获得该应用授权的发件地址。

4. 重新部署后测试

清理旧依赖后,重新构建并部署应用。如果只是从开发环境中删除 JAR,却没有重新生成部署包,旧库仍可能留在服务器端。

代码示例

import java.util.Properties;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

public boolean actualSend(String msgText, String subject) {
    Properties props = new Properties();
    Session session = Session.getDefaultInstance(props, null);

    try {
        Message msg = new MimeMessage(session);
        msg.setFrom(new InternetAddress("authorized-sender@example.com"));
        msg.addRecipient(
            Message.RecipientType.TO,
            new InternetAddress("recipient@example.com")
        );
        msg.setSubject(subject);
        msg.setText(msgText);

        Transport.send(msg);
        return true;
    } catch (AddressException e) {
        // 地址格式错误
        return false;
    } catch (MessagingException e) {
        // 发送失败,实际应用中应记录异常信息
        return false;
    }
}

排查时不要直接吞掉异常,否则只能得到 false,无法分辨问题是发件人未授权、配额耗尽,还是依赖配置错误。至少应记录异常:

} catch (MessagingException e) {
    e.printStackTrace();
    return false;
}

生产环境应使用项目现有的日志框架,不要长期保留 printStackTrace()。

注意事项

免费配额和 billing 是两回事。没有启用 billing,并不意味着邮件可以无限发送。应用仍会受到每日收件人数、管理员邮件及其他 Mail API 配额的限制。超过免费额度后,邮件可能发送失败,因此即使应用流量不大,也不能忽略配额监控。

App Engine 的功能和计费规则也会随运行环境和时间变化。如果使用的是较新的 App Engine runtime,而不是异常信息对应的经典运行环境,需要确认该 runtime 是否仍然支持内置 Mail API。如果当前环境已不再提供这项服务,就要改用第三方邮件服务或 SMTP。这些方案可能要求启用网络访问、billing,或者配置邮件服务商的 API 凭据。

备注:内容仅供参考。