yikart/AiToEarn · error · AppHttpException

ErrHttpBack.err_user_code_send_fail

ErrHttpBack.err_user_code_send_fail

Error message

err_user_code_send_fail

What it means

AppHttpException with ErrHttpBack.err_user_code_send_fail (errCode 40020, message '验证码发送失败'). Thrown by LoginService.postPhoneRegisterCode (login.service.ts:40) when NODE_ENV=production and alicloudSmsService.sendLoginSms returns a falsy result, meaning AliCloud SMS failed to send the code. In non-production the SMS call result is not enforced (the code is returned directly).

Source

Thrown at project/aitoearn-electron/server/src/user/login.service.ts:40

  constructor(
    private readonly redisService: RedisService,
    private readonly alicloudSmsService: AlicloudSmsService,
  ) {}

  /**
   * 发送手机号注册的验证码
   * @param phone
   */
  async postPhoneRegisterCode(phone: string) {
    const cacheKey = `${LoginTypeCacheKey.Password}:${phone}`;
    let code = await this.redisService.get(cacheKey);
    if (code) throw new AppHttpException(ErrHttpBack.err_user_code_had);

    code = getRandomString(6, true);
    const res = await this.alicloudSmsService.sendLoginSms(phone, code);

    if (process.env.NODE_ENV === 'production') {
      if (!res) throw new AppHttpException(ErrHttpBack.err_user_code_send_fail);
    }

    this.redisService.setKey(cacheKey, code, 60 * 5);
    console.log('发送短信成功', code);

    return process.env.NODE_ENV === 'production' ? res : code;
  }

  /**
   * 发送手机号登录的验证码
   * @param phone
   */
  async postPhoneLoginCode(phone: string) {
    const cacheKey = `${LoginTypeCacheKey.Code}:${phone}`;
    let code = await this.redisService.get(cacheKey);
    if (code) throw new AppHttpException(ErrHttpBack.err_user_code_had);

    code = getRandomString(6, true);

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Check AliCloud SMS credentials and configuration (access key, signature, template id) in the production env
  2. Inspect the AliCloud SMS API response/logs for the reject reason (throttle, invalid phone, blacklisted)
  3. Verify the SMS account has balance and the template/signature are approved
  4. Only set NODE_ENV=production after SMS is verified working, since enforcement only happens in production

Example fix

// env
// before
ALICLOUD_SMS_TEMPLATE=unset
// after
ALICLOUD_ACCESS_KEY=xxx
ALICLOUD_SMS_SIGN=AiToEarn
ALICLOUD_SMS_TEMPLATE=SMS_123456789
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-flight: verify SMS config is reachable before user flows
const cfgOk = !!(process.env.ALICLOUD_ACCESS_KEY && process.env.ALICLOUD_SMS_SIGN && process.env.ALICLOUD_SMS_TEMPLATE)
if (!cfgOk) throw new Error('AliCloud SMS configuration missing')

Type guard

function isSmsConfigured(cfg: Partial<SmsConfig>): cfg is SmsConfig {
  return Boolean(cfg.accessKey && cfg.sign && cfg.templateCode)
}

Try / catch

try {
  await api.postPhoneRegisterCode(phone)
} catch (e) {
  if (e?.errCode === '40020') {
    // log SMS failure details, surface a retry-with-backoff message to the user
  } else throw e
}

Prevention

When it happens

Trigger: Requesting a register SMS code in a production deployment where alicloudSmsService.sendLoginSms(phone, code) resolves to null/false — SMS provider rejects or fails the send.

Common situations: Missing or invalid AliCloud SMS credentials (accessKeyId/secret, sign name, template code); phone number rejected (invalid format, blacklist, throttling by AliCloud); SMS account balance exhausted or template not approved for the region.

Related errors


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/61399e7f7773a055. Report an issue: GitHub.