You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Twilio Studio/TwiML实现收听上一通来电语音邮件录音

嘿,很高兴帮你搞定这个需求!你想让呼叫者能播放上一位留下的语音邮件,不管用Twilio Studio还是TwiML都能实现,我给你详细说下两种方案:

Twilio Studio 实现方案

你提到的RecordingURL确实是核心,但Studio本身没法自动记住最新的录音地址,得借助Twilio的Functions + Sync来存储和获取最新录音URL,步骤如下:

1. 存储最新录音URL

在你的录音流程里,当Record组件完成录音后(也就是「Recording Completed」分支),添加一个「Run Function」组件,调用你写的Twilio Function,把当前录音的URL传进去:

  • 函数参数里加个recordingUrl,值填{{widgets.你的录音组件名称.RecordingUrl}}

这个Function的作用是把最新的录音URL存在Twilio Sync里(用Sync Document来存,相当于一个键值对),代码示例:

exports.handler = async function(context, event, callback) {
  const client = context.getTwilioClient();
  try {
    // 更新或创建名为latest_recording_url的Sync Document
    await client.sync.v1.services(context.SYNC_SERVICE_SID)
      .documents('latest_recording_url')
      .update({data: {url: event.recordingUrl}});
    callback(null, {status: "success"});
  } catch (err) {
    callback(err);
  }
};

2. 获取并播放最新录音

当呼叫者选择播放上一条留言时(比如用Gather组件让用户按某个键触发),先调用另一个Twilio Function去Sync里取最新的录音URL:

  • 函数返回后,把返回的recordingUrl赋值给Studio的流程变量,比如{{flow.data.latest_recording_url}}

然后用Play组件,把「音频文件URL」设置成这个变量{{flow.data.latest_recording_url}}就行啦。如果还没有录音,你可以在Function里返回null,然后在Studio里加个判断,用Say组件提示用户“目前没有留言”。

获取URL的Function代码示例:

exports.handler = async function(context, event, callback) {
  const client = context.getTwilioClient();
  try {
    const doc = await client.sync.v1.services(context.SYNC_SERVICE_SID)
      .documents('latest_recording_url')
      .fetch();
    callback(null, {recordingUrl: doc.data.url});
  } catch (err) {
    // 还没录音的情况,返回空值
    callback(null, {recordingUrl: null});
  }
};

TwiML 实现方案

如果Studio的配置让你觉得麻烦,用TwiML+后端代码会更灵活,核心逻辑和Studio类似,只是用代码来处理流程:

1. 初始引导TwiML

先给呼叫者返回带选项的TwiML,让用户选择播放或录音:

<Response>
  <Gather numDigits="1" action="/handle-choice">
    <Say>欢迎来电。按1播放上一条留言,按2录制新留言。</Say>
  </Gather>
</Response>

2. 处理用户选择

当用户按键后,后端根据选择执行不同操作:

  • 选1:从Sync(或你的数据库)取最新录音URL,用<Play>播放;如果没有录音,用<Say>提示
  • 选2:用<Record>组件让用户留言,录音完成后把RecordingURL存储到Sync

Node.js后端示例代码:

const express = require('express');
const twilio = require('twilio');
const app = express();
app.use(express.urlencoded({ extended: false }));

// 处理用户选择
app.post('/handle-choice', async (req, res) => {
  const twiml = new twilio.twiml.VoiceResponse();
  const choice = req.body.Digits;

  if (choice === '1') {
    // 获取最新录音URL
    const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
    try {
      const doc = await client.sync.v1.services(process.env.SYNC_SERVICE_SID)
        .documents('latest_recording_url')
        .fetch();
      twiml.play(doc.data.url);
    } catch (err) {
      twiml.say('目前没有留言,请稍后再试。');
    }
  } else if (choice === '2') {
    twiml.record({
      action: '/save-recording',
      finishOnKey: '#',
      maxLength: 60
    });
    twiml.say('请在提示音后留言,结束时按井号键。');
  }

  res.type('text/xml');
  res.send(twiml.toString());
});

// 保存录音URL
app.post('/save-recording', async (req, res) => {
  const recordingUrl = req.body.RecordingUrl;
  const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);

  // 存储到Sync
  await client.sync.v1.services(process.env.SYNC_SERVICE_SID)
    .documents('latest_recording_url')
    .update({data: {url: recordingUrl}});

  const twiml = new twilio.twiml.VoiceResponse();
  twiml.say('留言已保存,感谢来电!');
  res.type('text/xml');
  res.send(twiml.toString());
});

app.listen(3000, () => console.log('Server running on port 3000'));

注意事项

  • 要确保你的Twilio账户启用了Sync服务,并且在Function或后端代码里配置好SYNC_SERVICE_SID、TWILIO_ACCOUNT_SID和TWILIO_AUTH_TOKEN这些环境变量
  • 如果担心录音URL的安全性,可以用Twilio API生成带过期时间的签名URL,不过一般内部使用的话默认URL就够用了

内容的提问来源于stack exchange,提问作者Matt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 18:37:37