讲师中心 微信公众号
AI工具推荐 视频效率加速

实现Twilio掩码号码呼叫未接听时的语音留言功能

冬宇同学_4330

冬宇同学_4330

发布时间:2025-12-13 17:55:37

|

702人浏览过

|

来源于php中文网

原创

实现Twilio掩码号码呼叫未接听时的语音留言功能

本文详细介绍了如何为twilio掩码号码的呼叫转发功能实现语音留言回退机制。当客户拨打掩码号码,而转发至用户真实号码的呼叫未能接通(如无人接听、占线或不可达)时,系统将引导客户录制语音留言。教程涵盖了twiml dial 动词的超时配置、record 动词的使用,以及如何通过webhook回调处理录音,实现语音留言的存储、转文本和邮件通知。

Twilio掩码号码呼叫转发与语音留言回退机制

在构建基于Twilio的通信应用时,为用户提供掩码号码并实现呼叫转发是常见需求。然而,当被转发的用户无法接听电话时,提供一个语音留言选项能显著提升用户体验。本教程将指导您如何结合Twilio的TwiML(Twilio Markup Language)动词,实现这一高级功能:当客户拨打掩码号码,且呼叫转发至用户真实号码失败时,自动引导客户录制语音留言。

核心概念与Twilio TwiML动词

实现此功能主要依赖于Twilio的两个TwiML动词:

  1. <Dial> 动词与 timeout 属性:用于将当前呼叫连接到另一个电话号码。timeout 属性定义了Twilio在放弃尝试连接被叫方之前等待的时间(秒)。如果在此时间内被叫方未接听,Twilio将继续执行TwiML响应中的下一个动词。
  2. <Record> 动词与 recordingStatusCallback 属性:用于录制呼叫方的语音。recordingStatusCallback 属性指定一个URL,Twilio会在录音完成后向该URL发送HTTP请求,其中包含录音的详细信息和URL。

实现步骤

我们将基于现有的Express应用和Twilio Webhook配置进行扩展。

1. 配置呼叫转发与超时

首先,修改处理入站语音呼叫的/webhook/voice路由。在case "ringing"逻辑中,使用<Dial>动词尝试将呼叫转发到用户的真实号码。关键在于为<Dial>设置一个timeout属性。如果用户未在指定时间内接听,Twilio将执行TwiML响应中的下一个动词。

Skill Weave Chains — 技能链路由引擎
Skill Weave Chains — 技能链路由引擎

开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。

下载
const twilio = require("twilio");
const express = require("express");
const router = express.Router();

// 假设这些是您的数据库和邮件发送工具函数
const { getNumberWithoutUser, updateQuota } = require("../db/dbOperations");
const { sendMessageNotificationEmail } = require("../emailing/email");
const { sendSms, client } = require("../twilioFunctions");
const { appendMessage } = require("../db/messagingCollectionUtils");
const { appendCall } = require("../db/callsCollectionUtils");

router.post("/webhook/voice", async (req, res) => {
  const { To, From, CallStatus } = req.body;

  const [numbers] = await getNumberWithoutUser(To);
  if (!numbers) {
    console.warn(`User does not own number: ${To}`);
    return res.status(400).send("User does not own this number");
  }

  const activeSubscription = numbers.numbers.subscriptions.find(
    (subscription) => subscription.active
  );
  if (!activeSubscription) {
    console.warn(`No active subscription for number: ${To}`);
    return res.send("Call Forwarding is disabled or package has finished");
  }

  const type = activeSubscription.type;
  const isToPrimaryPhone = numbers?.numbers?.settings?.forwarding?.toPrimaryPhone;
  const primaryPhoneNumber = numbers?.numbers?.settings?.forwarding?.primaryPhoneNumber;

  console.log("CallStatus", CallStatus);

  if (isToPrimaryPhone && primaryPhoneNumber) {
    const twiml = new twilio.twiml.VoiceResponse();

    switch (CallStatus) {
      case "ringing":
        // 尝试拨打用户主号码,设置15秒超时
        // 如果在15秒内未接听,Twilio将继续执行TwiML响应中的下一个动词
        twiml.dial({ timeout: 15 }, primaryPhoneNumber);

        // 如果呼叫未接通(超时、占线、无人接听),则播放提示音并开始录音
        twiml.say("您拨打的用户当前无法接听,请在哔声后留言,按星号键结束。");
        twiml.record({
          recordingStatusCallback: "https://your-ngrok-url/webhook/voicemail-callback", // 替换为您的Webhook URL
          maxLength: 60, // 最长录音时间60秒
          finishOnKey: '*' // 按星号键结束录音
        });
        twiml.say("感谢您的留言,再见。"); // 录音结束后播放
        twiml.hangup(); // 结束通话

        await updateQuota(numbers._id, To, "callForwarding", type);
        res.type("text/xml");
        return res.send(twiml.toString());

      case "completed":
        // 呼叫成功完成,记录通话信息
        await appendCall(numbers._id, To, From, req.body);
        return res.send("success");

      // 可以根据需要处理其他CallStatus,例如 "no-answer", "busy", "failed"
      // 但对于语音留言回退,主要逻辑已在 'ringing' 状态的TwiML中处理
    }
  } else {
    console.log("Call Forwarding is disabled or primaryPhoneNumber is not set.");
  }
  res.send("Call Forwarding is disabled or package has finished");
});

// 导出router以供主应用使用
module.exports = router;

代码解释:

  • 在twiml.dial之后,我们紧接着添加了twiml.say和twiml.record。这是Twilio TwiML的关键行为:如果dial动词未能成功连接呼叫(例如,超时、占线、无人接听),Twilio会自动执行TwiML响应中的下一个动词。
  • timeout: 15:设置拨号超时为15秒。
  • recordingStatusCallback:这是最重要的部分,它指定了一个URL,Twilio会在录音完成后向该URL发送POST请求,包含录音文件的URL和其他元数据。请务必将其替换为您的实际Webhook地址。
  • maxLength:限制录音的最长时间。
  • finishOnKey:允许呼叫者通过按特定键(例如*)提前结束录音。

2. 处理语音留言回调

当客户完成录音或达到maxLength限制时,Twilio会向recordingStatusCallback指定的URL发送一个POST请求。您需要创建一个新的路由来处理这个回调。在这个路由中,您可以获取录音文件的URL,将其存储到数据库,并进一步处理(例如,使用Twilio的Speech-to-Text API进行转录,然后通过邮件发送给用户)。

// ... (其他引入和router定义) ...

router.post("/webhook/voicemail-callback", async (req, res) => {
  const { RecordingUrl, CallSid, From, To, RecordingDuration, TranscriptionText } = req.body;

  console.log("Voicemail Callback Received:");
  console.log(`Recording URL: ${RecordingUrl}`);
  console.log(`Call SID: ${CallSid}`);
  console.log(`From: ${From}, To: ${To}`);
  console.log(`Duration: ${RecordingDuration} seconds`);
  console.log(`Transcription: ${TranscriptionText || "Not available or not requested"}`);

  try {
    // 1. 获取掩码号码对应的用户信息
    const [numbers] = await getNumberWithoutUser(To);
    if (!numbers) {
      console.error(`Voicemail: User does not own number: ${To}`);
      return res.status(400).send("User does not own this number");
    }

    // 2. 将语音留言信息存储到数据库
    // 假设您有一个 appendVoicemail 函数来处理此逻辑
    await appendVoicemail(numbers._id, To, From, {
      recordingUrl: RecordingUrl,
      duration: RecordingDuration,
      callSid: CallSid,
      // 如果您在Record动词中启用了转录,TranscriptionText 会在这里
      transcription: TranscriptionText || null,
      timestamp: new Date(),
    });

    // 3. 将语音留言转录为文本(如果未在Record动词中启用)并发送邮件
    // Twilio的Record动词可以直接进行转录,但如果需要更高级的转录或后续处理,可以在这里调用Twilio的Transcription API
    let finalTranscription = TranscriptionText;
    if (!finalTranscription && RecordingUrl) {
      // 示例:手动调用Twilio API进行转录 (需要配置client)
      // const transcription = await client.transcriptions.create({
      //   recordingSid: RecordingUrl.split('/').pop().split('.')[0] // 从URL中提取Recording SID
      // });
      // finalTranscription = transcription.transcriptionText;
      // console.log("Manual Transcription:", finalTranscription);
    }

    // 4. 将语音留言(或其转录文本)通过邮件发送给用户
    const userEmail = numbers?.numbers?.settings?.emailForVoicemail || numbers?.user?.email; // 假设用户邮箱配置在这里
    if (userEmail) {
      await sendMessageNotificationEmail(
        userEmail,
        `您有一个来自 ${From} 的新语音留言`,
        `您收到一个新语音留言。时长:${RecordingDuration}秒。\n\n` +
        `语音链接:${RecordingUrl}.mp3\n\n` + // Twilio录音URL通常支持.mp3扩展
        (finalTranscription ? `转录文本:\n${finalTranscription}` : "无转录文本。")
      );
      console.log(`Voicemail sent to user email: ${userEmail}`);
    } else {
      console.warn(`No email configured for user associated with number: ${To}`);
    }

    res.status(200).send("Voicemail callback processed successfully");
  } catch (error) {
    console.error("Error processing voicemail callback:", error);
    res.status(500).send("Error processing voicemail callback");
  }
});

// 辅助函数,需要您在 dbOperations.js 中实现
async function appendVoicemail(userId, maskedNumber, fromNumber, voicemailData) {
  // 实际的数据库操作,将语音留言数据存储到您的DB中
  console.log(`Storing voicemail for user ${userId} from ${fromNumber} on ${maskedNumber}:`, voicemailData);
  // 示例: await db.collection('voicemails').insertOne({ userId, maskedNumber, fromNumber, ...voicemailData });
  return Promise.resolve(); // 模拟成功
}

// ... (导出router) ...

代码解释:

  • RecordingUrl:这是录音文件的URL,您可以直接播放或下载。Twilio通常提供多种格式,添加.mp3后缀可以直接获取MP3格式。
  • TranscriptionText:如果您的Record动词配置了转录功能(通过设置transcribe: true),转录的文本会直接包含在此参数中。
  • appendVoicemail:这是一个占位函数,您需要根据您的数据库结构实现将录音信息(URL、时长、来电号码等)存储到数据库的逻辑。
  • sendMessageNotificationEmail:这是一个占位函数,用于向用户发送包含语音留言链接和转录文本的邮件通知。

注意事项与最佳实践

  1. Webhook URL安全性:您的Webhook URL应该通过HTTPS进行保护,以防止中间人攻击。Twilio还提供了Webhook请求签名验证功能,强烈建议您实现它来确保请求确实来自Twilio。
  2. 错误处理:在您的Webhook路由中,务必实现健壮的错误处理机制,以应对数据库操作失败、邮件发送失败等情况。
  3. 用户体验:
    • twiml.say的提示语应该清晰明了,告知客户当前情况以及如何留言和结束录音。
    • maxLength和finishOnKey的设置应合理,平衡用户留言的需求和系统资源的消耗。
  4. Speech-to-Text API:Twilio的Record动词可以直接进行语音转文本。您可以在twiml.record中添加transcribe: true和transcribeCallback属性来启用和处理转录。如果需要更高级的转录模型或语言支持,也可以在voicemail-callback中手动调用Twilio的Transcription API或集成第三方服务。
  5. 配额管理:确保您在updateQuota函数中正确更新了用户的呼叫和语音留言配额。
  6. 可扩展性:随着用户量的增长,确保您的数据库和邮件服务能够处理增加的负载。

总结

通过以上步骤,您已经成功为您的Twilio掩码号码呼叫转发系统添加了语音留言回退功能。这不仅提升了用户体验,也确保了客户在无法直接联系到用户时,仍能有效地传达信息。结合Twilio强大的TwiML和Webhook机制,您可以构建出高度灵活和功能丰富的通信应用。

热门AI工具

更多
讯飞绘文

讯飞绘文是一款由科大讯飞推出的一站式 AIGC 内容运营平台。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

切问学术

切问学术是一款AI论文写作工具,复旦大学NLP团队推出的AI学术智能体。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

AionClaw
AionClaw Hot

AionClaw是一款面向办公、创作和编程任务的AI桌面智能体。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

相关专题

更多
Node.js后端开发与Express框架实践
Node.js后端开发与Express框架实践

本专题针对初中级 Node.js 开发者,系统讲解如何使用 Express 框架搭建高性能后端服务。内容包括路由设计、中间件开发、数据库集成、API 安全与异常处理,以及 RESTful API 的设计与优化。通过实际项目演示,帮助开发者快速掌握 Node.js 后端开发流程。

741

2026.02.10

数据库三范式
数据库三范式

数据库三范式是一种设计规范,用于规范化关系型数据库中的数据结构,它通过消除冗余数据、提高数据库性能和数据一致性,提供了一种有效的数据库设计方法。本专题提供数据库三范式相关的文章、下载和课程。

2345

2023.06.29

如何删除数据库
如何删除数据库

删除数据库是指在MySQL中完全移除一个数据库及其所包含的所有数据和结构,作用包括:1、释放存储空间;2、确保数据的安全性;3、提高数据库的整体性能,加速查询和操作的执行速度。尽管删除数据库具有一些好处,但在执行任何删除操作之前,务必谨慎操作,并备份重要的数据。删除数据库将永久性地删除所有相关数据和结构,无法回滚。

3661

2023.08.14

vb怎么连接数据库
vb怎么连接数据库

在VB中,连接数据库通常使用ADO(ActiveX 数据对象)或 DAO(Data Access Objects)这两个技术来实现:1、引入ADO库;2、创建ADO连接对象;3、配置连接字符串;4、打开连接;5、执行SQL语句;6、处理查询结果;7、关闭连接即可。

2511

2023.08.31

MySQL恢复数据库
MySQL恢复数据库

MySQL恢复数据库的方法有使用物理备份恢复、使用逻辑备份恢复、使用二进制日志恢复和使用数据库复制进行恢复等。本专题为大家提供MySQL数据库相关的文章、下载、课程内容,供大家免费下载体验。

847

2023.09.05

vb中怎么连接access数据库
vb中怎么连接access数据库

vb中连接access数据库的步骤包括引用必要的命名空间、创建连接字符串、创建连接对象、打开连接、执行SQL语句和关闭连接。本专题为大家提供连接access数据库相关的文章、下载、课程内容,供大家免费下载体验。

2267

2023.10.09

数据库对象名无效怎么解决
数据库对象名无效怎么解决

数据库对象名无效解决办法:1、检查使用的对象名是否正确,确保没有拼写错误;2、检查数据库中是否已存在具有相同名称的对象,如果是,请更改对象名为一个不同的名称,然后重新创建;3、确保在连接数据库时使用了正确的用户名、密码和数据库名称;4、尝试重启数据库服务,然后再次尝试创建或使用对象;5、尝试更新驱动程序,然后再次尝试创建或使用对象。

2287

2023.10.16

vb连接access数据库的方法
vb连接access数据库的方法

vb连接access数据库方法:1、使用ADO连接,首先导入System.Data.OleDb模块,然后定义一个连接字符串,接着创建一个OleDbConnection对象并使用Open() 方法打开连接;2、使用DAO连接,首先导入 Microsoft.Jet.OLEDB模块,然后定义一个连接字符串,接着创建一个JetConnection对象并使用Open()方法打开连接即可。

2793

2023.10.16

Buffalo框架数据库开发全教程
Buffalo框架数据库开发全教程

本专题围绕Buffalo框架数据库开发,讲解database.yml多环境配置、soda与fizz迁移生成回滚、模型结构体标签、增删改查与条件查询、一对多与多对多关联、数据校验、回调钩子、事务处理及原生SQL执行能力。

160

2026.09.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WEB前端教程【HTML5+CSS3+JS】
WEB前端教程【HTML5+CSS3+JS】

共101课时 | 20.6万人学习

JS进阶与BootStrap学习
JS进阶与BootStrap学习

共39课时 | 4.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn