{"id":211311,"date":"2026-09-25T10:24:16","date_gmt":"2026-09-25T10:24:16","guid":{"rendered":"https:\/\/sinch.com\/blog\/como-construi-stack-comunicacoes-cursor-plugin-sinch\/"},"modified":"2026-09-25T10:24:16","modified_gmt":"2026-09-25T10:24:16","slug":"como-construi-stack-comunicacoes-cursor-plugin-sinch","status":"publish","type":"blog","link":"https:\/\/sinch.com\/pt\/blog\/como-construi-stack-comunicacoes-cursor-plugin-sinch\/","title":{"rendered":"Como constru\u00ed uma stack de comunica\u00e7\u00f5es no Cursor com o plugin da Sinch"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Uma integra\u00e7\u00e3o de comunica\u00e7\u00f5es raramente \u00e9 lenta por causa do envio em si. O tempo \u00e9 gasto com a estrutura do payload, um\u00a0esquema de autentica\u00e7\u00e3o diferente para cada produto e a dist\u00e2ncia entre algo que funciona em staging e\u00a0algo que funciona no celular em suas m\u00e3os.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">J\u00e1 criei integra\u00e7\u00f5es suficientes nas APIs da Sinch para saber onde esse tempo \u00e9 gasto, o que torna o\u00a0<a href=\"https:\/\/cursor.com\/marketplace\/sinch\/sinch-cursor-plugin\" target=\"_blank\" rel=\"noreferrer noopener\">plugin da Sinch para o Cursor<\/a> vale a pena testar\u00a0adequadamente: consigo dizer se o c\u00f3digo que um agente entrega est\u00e1 correto. Ent\u00e3o tirei uma tarde\u00a0e usei um banco de demonstra\u00e7\u00e3o como base.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">O RockBank \u00e9 esse banco de demonstra\u00e7\u00e3o: uma institui\u00e7\u00e3o de cr\u00e9dito fict\u00edcia cujos clientes ignoram os lembretes de pagamento enviados por correspond\u00eancia.\u00a0A miss\u00e3o era transferir os lembretes para os canais que as pessoas de fato leem, e manter todo o\u00a0envio de mensagens por tr\u00e1s de um s\u00f3 caminho de envio e endpoint de webhook, para que a adi\u00e7\u00e3o de um canal fosse uma mudan\u00e7a de configura\u00e7\u00e3o\u00a0em vez de uma reescrita do c\u00f3digo.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">O que o fluxo faz<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Um lembrete de pagamento por SMS enviado pela Sinch Conversation API<\/li>\n\n\n\n<li>O mesmo lembrete com upgrade para RCS, com um remetente verificado e um bot\u00e3o \u201cPagar agora\u201d na thread<\/li>\n\n\n\n<li>Um lembrete de voz automatizado se o pagamento continuar pendente<\/li>\n\n\n\n<li>Verifica\u00e7\u00e3o de identidade com uma senha de uso \u00fanico antes de processar o pagamento<\/li>\n\n\n\n<li>Um e-mail de confirma\u00e7\u00e3o pelo Mailgun depois que o pagamento for conclu\u00eddo<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Isso representa o envio de mensagens, voz, verifica\u00e7\u00e3o e e-mail por tr\u00e1s de um caminho de envio e um webhook de entrada.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">O que foi necess\u00e1rio para o projeto<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">O c\u00f3digo do RockBank n\u00e3o foi publicado, ent\u00e3o este n\u00e3o \u00e9 um projeto que basta clonar e executar. A seguir, mostramos a configura\u00e7\u00e3o de conta\u00a0que os snippets presumem, e a mesma configura\u00e7\u00e3o de que voc\u00ea precisaria para criar sua pr\u00f3pria vers\u00e3o:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Cursor com o plugin da Sinch instalado e um n\u00famero de telefone de teste que seja seu<\/li>\n\n\n\n<li>Um app da Conversation API: ID do projeto, ID da chave, segredo da chave e ID do app<\/li>\n\n\n\n<li>Credenciais separadas por produto: uma chave e um segredo do aplicativo de voz com um n\u00famero atribu\u00eddo a ele,\u00a0e uma chave e um segredo do app de verifica\u00e7\u00e3o, que, por sua vez, \u00e9 um app \u00e0 parte<\/li>\n\n\n\n<li>Um agente de RCS aprovado para o seu mercado, com o SMS configurado no mesmo app para fallback. A aprova\u00e7\u00e3o da operadora\u00a0leva tempo, ent\u00e3o comece logo. Um remetente de SMS n\u00e3o \u00e9 um substituto: sem um agente aprovado,\u00a0todos os lembretes chegam como texto simples.<\/li>\n\n\n\n<li><a href=\"https:\/\/developers.sinch.com\/docs\/10dlc-registration\/\" target=\"_blank\" rel=\"noreferrer noopener\">Registro 10DLC<\/a> em andamento, se voc\u00ea enviar para\u00a0n\u00fameros dos EUA. Leia mais sobre isso na se\u00e7\u00e3o de pegadinhas (Gotchas).<\/li>\n<\/ul>\n\n\n\r\n    <nav data-content-type=\"longform\" class=\"toc-block longform-spacings px-5 py-6 px-md-6 px-lg-7 py-md-7 bg-light fs-sm rounded-lg\" aria-labelledby=\"toc-title-9418\"><p class=\"h5 m-0\" id=\"toc-title-9418\">\u00cdndice<\/p><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">01<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#o-que-o-fluxo-faz\">O que o fluxo faz<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">02<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#o-que-foi-necessario-para-o-projeto\">O que foi necess\u00e1rio para o projeto<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">03<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#configurando-o-plugin\">Configurando o plugin<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">04<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#construcao-do-caminho-de-envio-na-conversation-api\">Constru\u00e7\u00e3o do caminho de envio na Conversation API<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">05<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#testes-usando-o-editor\">Testes usando o editor<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">06<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#integracao-do-webhook-de-entrada\">Integra\u00e7\u00e3o do webhook de entrada<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">07<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#adicao-de-rcs\">Adi\u00e7\u00e3o de RCS<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">08<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#voz-verificacao-e-e-mail\">Voz, verifica\u00e7\u00e3o e e-mail<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">09<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#pegadinhas-gotchas\">Pegadinhas (Gotchas)<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">10<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#qual-foi-o-resultado-final\">Qual foi o resultado final<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">11<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#experimente\">Experimente<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">12<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#recursos-adicionais\">Recursos adicionais<\/a><\/div><\/div><\/nav>\n\n\n<h2 class=\"wp-block-heading\">Configurando o plugin<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Instale no marketplace do Cursor ou pela paleta de comandos:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-light\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p><!-- \/wp:paragraph --><\/p><p><!-- \/wp:paragraph --><\/p><p class=\"mb-0\">\/add-plugin sinch-cursor-plugin<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">O plugin tem tr\u00eas partes, e elas realizam trabalhos diferentes:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>As <strong>Skills<\/strong> carregam o conhecimento da API da Sinch no contexto do agente antes de ele programar: endpoints, esquemas de autentica\u00e7\u00e3o, formas do payload e propriedades do canal. Essa \u00e9 a parte que decide se o c\u00f3digo gerado \u00e9 compilado na API real ou em uma inven\u00e7\u00e3o plaus\u00edvel.<\/li>\n\n\n\n<li>Os <strong>comandos<\/strong> (como <code>\/send-message<\/code> e <code>\/list-webhooks<\/code>) chamam as APIs da Sinch diretamente do chat, sem nenhum c\u00f3digo entre eles.<\/li>\n\n\n\n<li><strong>Os servidores MCP<\/strong>, dois deles. <code>sinch<\/code> executa <code>npx -y @sinch\/mcp<\/code> localmente e conecta o agente \u00e0 sua conta ativa para que ele possa enviar mensagens e inspecionar a configura\u00e7\u00e3o enquanto voc\u00ea trabalha. <code>sinch-docs<\/code> \u00e9 remoto, em <code>developers.sinch.com\/mcp<\/code>, para pesquisar a documenta\u00e7\u00e3o. S\u00f3 o primeiro aceita credenciais.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">As credenciais s\u00e3o a \u00fanica parte da configura\u00e7\u00e3o que exige bastante precis\u00e3o. Cinco vari\u00e1veis entram em um bloco <code>env<\/code> em <code>~\/.cursor\/settings.json<\/code>, e o Cursor precisa ser reiniciado para reconhec\u00ea-las:<\/p>\n\n\n    <div data-content-type=\"longform\" class=\"code-snippet longform-spacings rounded-lg overflow-hidden shadow \" data-count=\"1\">\r\n        <ul class=\"nav nav-buttons code-snippet__tabs longform-except position-relative d-flex gap-2 flex-wrap p-3\" role=\"tablist\">\r\n\r\n                            \r\n                    \r\n                        <li class=\"nav-item\" role=\"presentation\">\r\n                            <button class=\"nav-link active\" data-bs-toggle=\"tab\" data-bs-target=\"#code_0\" type=\"button\" role=\"tab\" aria-controls=\"code_0\" aria-selected=\"true\">\r\n                                Shell                            <\/button>\r\n                        <\/li>\r\n\r\n                    \r\n                            \r\n        <\/ul>\r\n        <div class=\"code-snippet__tab-content tab-content\">\r\n\r\n                            \r\n                    <div class=\"tab-pane show active\" id=\"code_0\" role=\"tabpanel\" aria-labelledby=\"code_0\">\r\n                        <div class=\"code-snippet__code-wrapper\">\r\n                            <div class=\"nav nav-buttons code-snippet__copy-wrapper\">\r\n                                <button type=\"button\" class=\"nav-link code-snippet__copy\" data-clipboard-target=\"#code_0_content\" data-clipboard-label=\"Copiar para a \u00e1rea de transfer\u00eancia\" data-clipboard-success-label=\"Copiado!\" data-clipboard-error-label=\"N\u00e3o foi poss\u00edvel copiar\" data-tooltip=\"Copiar para a \u00e1rea de transfer\u00eancia\" data-tooltip-placement=\"left\" role=\"tooltip\" aria-label=\"Copiar para a \u00e1rea de transfer\u00eancia\">\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--default\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/file-stack.svg\"><\/svg>\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--success\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/badge-check.svg\"><\/svg>\r\n                                <\/button>\r\n                            <\/div>\r\n                            <pre class=\"w-100 h-100 m-0 line-numbers language-shell\">\r\n                                <code id=\"code_0_content\" class=\"language-shell\">\r\n\r\n                                    {\r\n  &quot;env&quot;: {\r\n    &quot;CONVERSATION_PROJECT_ID&quot;: &quot;your-project-id&quot;,\r\n    &quot;CONVERSATION_KEY_ID&quot;: &quot;your-key-id&quot;,\r\n    &quot;CONVERSATION_KEY_SECRET&quot;: &quot;your-key-secret&quot;,\r\n    &quot;CONVERSATION_REGION&quot;: &quot;us&quot;,\r\n    &quot;CONVERSATION_APP_ID&quot;: &quot;your-app-id&quot;\r\n  }\r\n}\r\n                                <\/code>\r\n                            <\/pre>\r\n                        <\/div>\r\n                    <\/div>\r\n                                    <\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Se o servidor ainda informar a falta de credenciais, exporte as mesmas cinco vari\u00e1veis no seu perfil\u00a0do shell e feche totalmente para reiniciar em seguida: ele \u00e9 executado como <code>npx -y @sinch\/mcp<\/code> e herda o ambiente\u00a0do shell no qual o Cursor foi iniciado.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Essas cinco pertencem ao servidor MCP e abrangem a Conversation API; portanto, a chave e o segredo de voz\u00a0nunca entram no Cursor. As chamadas de voz s\u00f3 acontecem quando seu pr\u00f3prio c\u00f3digo \u00e9 executado.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Confirme a conex\u00e3o antes de programar qualquer coisa:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-light\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p class=\"mb-0\">\/send-message &#8211;to=+15551234567 &#8211;message=&#8221;Teste do RockBank&#8221;<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">O mesmo comando usa uma cadeia de fallback, que \u00e9 uma pr\u00e9-visualiza\u00e7\u00e3o \u00fatil do que o caminho de envio far\u00e1\u00a0depois:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-light\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p class=\"mb-0\">\/send-message &#8211;to=+15551234567 &#8211;message=&#8221;Teste do RockBank&#8221; &#8211;fallback=RCS,SMS<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<h2 class=\"wp-block-heading\">Constru\u00e7\u00e3o do caminho de envio na Conversation API<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">O formato do caminho de envio \u00e9 a decis\u00e3o da qual todo o resto depende. A Conversation API recebe\u00a0um corpo de mensagem gen\u00e9rico e o transcodifica por canal, e ela trata a sele\u00e7\u00e3o de canal e o fallback\u00a0como dados na solicita\u00e7\u00e3o em vez de ramifica\u00e7\u00f5es no c\u00f3digo. Para o RockBank, esse \u00e9 o design completo:\u00a0o lembrete \u00e9 enviado por RCS com o SMS atr\u00e1s, a voz entra depois, e o local da chamada nunca fica sabendo\u00a0de nada disso.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Vale a pena colocar isso no prompt em vez de deixar para ser inferido. Se pedir a um agente para &#8220;enviar SMS aos clientes\u00a0quando um pagamento estiver vencendo&#8221;, provavelmente voc\u00ea receber\u00e1 um caminho de envio moldado em torno de um canal,\u00a0pois foi o que voc\u00ea pediu. Em vez disso, informe a inten\u00e7\u00e3o e deixe o mecanismo por conta da Skill:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-sand\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p class=\"mb-0\">Implemente o caminho de envio do lembrete usando a Sinch Conversation API. Envie RCS com o SMS como fallback, e mantenha a interface est\u00e1vel para que a adi\u00e7\u00e3o de um canal mais tarde n\u00e3o altere os locais de chamada. Tamb\u00e9m processe confirma\u00e7\u00f5es de entrega e registre-as.<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Nada de nomes de campos, nem endpoint, nem esquema de autentica\u00e7\u00e3o. A Skill da <code>conversation-api<\/code> j\u00e1 explica como o fallback\u00a0funciona: adicione uma matriz <code>channel_priority_order<\/code> e liste as identidades de cada canal no destinat\u00e1rio. Essa\u00a0divis\u00e3o \u00e9 a meta para seus pr\u00f3prios prompts. Voc\u00ea descreve o comportamento que deseja e as\u00a0restri\u00e7\u00f5es que deve manter, e a Skill fornece o payload.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Essa \u00faltima cl\u00e1usula realmente trabalha. Sem ela, voc\u00ea ter\u00e1 um caminho de envio sem observabilidade, e descobrir\u00e1\u00a0como as coisas est\u00e3o indo quando um cliente avisar, pela primeira vez, que o lembrete nunca chegou. Com ela, o agente precisa\u00a0levar em conta o que ocorre depois que <code>messages:send<\/code> retorna um c\u00f3digo 200, que \u00e9 onde as falhas mais interessantes\u00a0se encontram.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">O que retornou foi um m\u00e9todo privado que aceita qualquer corpo de mensagem da Conversation API, com o fallback\u00a0expresso como <code>channel_priority_order<\/code> e as duas identidades de canal no destinat\u00e1rio:<\/p>\n\n\n    <div data-content-type=\"longform\" class=\"code-snippet longform-spacings rounded-lg overflow-hidden shadow \" data-count=\"1\">\r\n        <ul class=\"nav nav-buttons code-snippet__tabs longform-except position-relative d-flex gap-2 flex-wrap p-3\" role=\"tablist\">\r\n\r\n                            \r\n                    \r\n                        <li class=\"nav-item\" role=\"presentation\">\r\n                            <button class=\"nav-link active\" data-bs-toggle=\"tab\" data-bs-target=\"#code_0\" type=\"button\" role=\"tab\" aria-controls=\"code_0\" aria-selected=\"true\">\r\n                                Shell                            <\/button>\r\n                        <\/li>\r\n\r\n                    \r\n                            \r\n        <\/ul>\r\n        <div class=\"code-snippet__tab-content tab-content\">\r\n\r\n                            \r\n                    <div class=\"tab-pane show active\" id=\"code_0\" role=\"tabpanel\" aria-labelledby=\"code_0\">\r\n                        <div class=\"code-snippet__code-wrapper\">\r\n                            <div class=\"nav nav-buttons code-snippet__copy-wrapper\">\r\n                                <button type=\"button\" class=\"nav-link code-snippet__copy\" data-clipboard-target=\"#code_0_content\" data-clipboard-label=\"Copiar para a \u00e1rea de transfer\u00eancia\" data-clipboard-success-label=\"Copiado!\" data-clipboard-error-label=\"N\u00e3o foi poss\u00edvel copiar\" data-tooltip=\"Copiar para a \u00e1rea de transfer\u00eancia\" data-tooltip-placement=\"left\" role=\"tooltip\" aria-label=\"Copiar para a \u00e1rea de transfer\u00eancia\">\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--default\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/file-stack.svg\"><\/svg>\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--success\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/badge-check.svg\"><\/svg>\r\n                                <\/button>\r\n                            <\/div>\r\n                            <pre class=\"w-100 h-100 m-0 line-numbers language-shell\">\r\n                                <code id=\"code_0_content\" class=\"language-shell\">\r\n\r\n                                    async def _send(\r\n    self,\r\n    to: str,\r\n    message: dict[str, Any],\r\n    channel_properties: dict[str, str] | None = None,\r\n) -&gt; dict[str, Any]:\r\n    &quot;&quot;&quot;Send any Conversation API message over RCS, falling back to SMS.&quot;&quot;&quot;\r\n    properties = {&quot;SMS_SENDER&quot;: self.sms_sender} if self.sms_sender else {}\r\n    properties.update(channel_properties or {})\r\n\r\n    payload: dict[str, Any] = {\r\n        &quot;app_id&quot;: self.app_id,\r\n        &quot;recipient&quot;: {\r\n            &quot;identified_by&quot;: {\r\n                &quot;channel_identities&quot;: [\r\n                    {&quot;channel&quot;: &quot;RCS&quot;, &quot;identity&quot;: to},\r\n                    {&quot;channel&quot;: &quot;SMS&quot;, &quot;identity&quot;: to},\r\n                ]\r\n            }\r\n        },\r\n        &quot;channel_priority_order&quot;: [&quot;RCS&quot;, &quot;SMS&quot;],\r\n        &quot;message&quot;: message,\r\n    }\r\n    if properties:\r\n        payload[&quot;channel_properties&quot;] = properties\r\n\r\n    path = f&quot;\/v1\/projects\/{self.project_id}\/messages:send&quot;\r\n    response = await self.client.post(path, auth=self._auth, json=payload)\r\n    if response.is_error:\r\n        raise SinchClientError(\r\n            f&quot;messages:send failed ({response.status_code}): {response.text}&quot;\r\n        )\r\n    return response.json()\r\n\r\n\r\nasync def send_reminder(self, to: str, text: str) -&gt; dict[str, Any]:\r\n    return await self._send(to, {&quot;text_message&quot;: {&quot;text&quot;: text}})\r\n                                <\/code>\r\n                            <\/pre>\r\n                        <\/div>\r\n                    <\/div>\r\n                                    <\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Cada canal depois deste altera o corpo da <code>message<\/code> que entrega a <code>_send<\/code>, e mais nada.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A propriedade do canal <code>SMS_SENDER<\/code> est\u00e1 presente porque o SMS precisa de um originador, a menos que o app j\u00e1 tenha um\u00a0configurado como padr\u00e3o. Isso tamb\u00e9m veio da Skill.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Testes usando o editor<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u00c9 para essa parte que o servidor MCP serve. Com o cliente programado e o arquivo ainda aberto, pedi\u00a0um envio real:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-sand\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p><!-- \/wp:paragraph --><\/p><p class=\"mb-0\">Envie um lembrete de teste por meio desse servi\u00e7o para o meu n\u00famero, com uma data de vencimento daqui a tr\u00eas dias e um valor de US$\u00a0240.<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Uma mensagem ativa por meio de uma conta ativa, pelo editor:<\/p>\n\n\n    <div data-content-type=\"longform\" class=\"code-snippet longform-spacings rounded-lg overflow-hidden shadow \" data-count=\"1\">\r\n        <ul class=\"nav nav-buttons code-snippet__tabs longform-except position-relative d-flex gap-2 flex-wrap p-3\" role=\"tablist\">\r\n\r\n                            \r\n                    \r\n                        <li class=\"nav-item\" role=\"presentation\">\r\n                            <button class=\"nav-link active\" data-bs-toggle=\"tab\" data-bs-target=\"#code_0\" type=\"button\" role=\"tab\" aria-controls=\"code_0\" aria-selected=\"true\">\r\n                                Shell                            <\/button>\r\n                        <\/li>\r\n\r\n                    \r\n                            \r\n        <\/ul>\r\n        <div class=\"code-snippet__tab-content tab-content\">\r\n\r\n                            \r\n                    <div class=\"tab-pane show active\" id=\"code_0\" role=\"tabpanel\" aria-labelledby=\"code_0\">\r\n                        <div class=\"code-snippet__code-wrapper\">\r\n                            <div class=\"nav nav-buttons code-snippet__copy-wrapper\">\r\n                                <button type=\"button\" class=\"nav-link code-snippet__copy\" data-clipboard-target=\"#code_0_content\" data-clipboard-label=\"Copiar para a \u00e1rea de transfer\u00eancia\" data-clipboard-success-label=\"Copiado!\" data-clipboard-error-label=\"N\u00e3o foi poss\u00edvel copiar\" data-tooltip=\"Copiar para a \u00e1rea de transfer\u00eancia\" data-tooltip-placement=\"left\" role=\"tooltip\" aria-label=\"Copiar para a \u00e1rea de transfer\u00eancia\">\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--default\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/file-stack.svg\"><\/svg>\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--success\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/badge-check.svg\"><\/svg>\r\n                                <\/button>\r\n                            <\/div>\r\n                            <pre class=\"w-100 h-100 m-0 line-numbers language-shell\">\r\n                                <code id=\"code_0_content\" class=\"language-shell\">\r\n\r\n                                    {\r\n  &quot;message_id&quot;: &quot;01KY292FGM58WNNBETKE1Q4NF8&quot;,\r\n  &quot;accepted_time&quot;: &quot;2026-07-21T12:05:38.964Z&quot;\r\n}\r\n                                <\/code>\r\n                            <\/pre>\r\n                        <\/div>\r\n                    <\/div>\r\n                                    <\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Essa resposta significa que ela foi aceita, e n\u00e3o entregue. A entrega ocorre de forma ass\u00edncrona e aparece no\u00a0webhook. Sendo assim, a pr\u00f3xima etapa \u00e9 certificar-se de que o webhook realmente a assinou.<br><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Integra\u00e7\u00e3o do webhook de entrada<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Um endpoint transporta ambas as dire\u00e7\u00f5es de tr\u00e1fego: <code>MESSAGE_DELIVERY<\/code> para confirma\u00e7\u00f5es de entrega e\u00a0<code>MESSAGE_INBOUND<\/code> para respostas de clientes. Registre isso com um comando:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-light\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p class=\"mb-0\">\/create-webhook &#8211;target=https:\/\/rockbank.example.com\/sinch\/callbacks \\<br>  &#8211;triggers=MESSAGE_DELIVERY,MESSAGE_INBOUND &#8211;secret=$SINCH_WEBHOOK_SECRET<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">O alvo precisa ser HTTPS e acess\u00edvel pela Internet. Portanto, para o trabalho local, coloque um t\u00fanel antes\u00a0do manipulador e registre a URL desse t\u00fanel. O segredo \u00e9 opcional na API, mas passe um: \u00e9\u00a0ele que assina os callbacks, que precisa corresponder ao valor em compara\u00e7\u00e3o ao qual o seu manipulador faz a verifica\u00e7\u00e3o, e n\u00e3o h\u00e1\u00a0assinatura a verificar sem ele. Um app aceita at\u00e9 cinco webhooks, e registrar novamente o mesmo alvo\u00a0retorna um erro 400.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Em seguida, verifique o que o app realmente assinou, que nem sempre \u00e9 o que voc\u00ea acha que pediu:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-light\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p class=\"mb-0\">\/list-webhooks<br>\/list-webhook-triggers<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Vale a pena fazer isso antes de confiar em qualquer coisa posterior (downstream). Um webhook registrado apenas com <code>MESSAGE_INBOUND<\/code>\u00a0aceita seu registro sem problemas, e em seguida, o seu manipulador de confirma\u00e7\u00f5es de entrega fica inativo e nunca roda.\u00a0O caminho de envio parece \u00edntegro, mas o log de entrega fica vazio.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">O pr\u00f3prio manipulador exige a verifica\u00e7\u00e3o da assinatura antes de examinar o corpo:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-sand\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p class=\"mb-0\">Escreva o manipulador da FastAPI para o endpoint <code>\/sinch\/callbacks<\/code>. Verifique a assinatura HMAC-SHA256 que a Sinch envia em cada callback antes de processar o corpo, rejeite as solicita\u00e7\u00f5es com carimbo de data\/hora obsoleto ou reutilizado, e roteie as confirma\u00e7\u00f5es de entrega e as mensagens de entrada para manipuladores separados.<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">A Sinch assina os <a href=\"https:\/\/developers.sinch.com\/docs\/conversation\/callbacks\" target=\"_blank\" rel=\"noreferrer noopener\">callbacks<\/a> com HMAC-SHA256\u00a0sobre o corpo bruto, o nonce e o carimbo de data\/hora:<\/p>\n\n\n    <div data-content-type=\"longform\" class=\"code-snippet longform-spacings rounded-lg overflow-hidden shadow \" data-count=\"1\">\r\n        <ul class=\"nav nav-buttons code-snippet__tabs longform-except position-relative d-flex gap-2 flex-wrap p-3\" role=\"tablist\">\r\n\r\n                            \r\n                    \r\n                        <li class=\"nav-item\" role=\"presentation\">\r\n                            <button class=\"nav-link active\" data-bs-toggle=\"tab\" data-bs-target=\"#code_0\" type=\"button\" role=\"tab\" aria-controls=\"code_0\" aria-selected=\"true\">\r\n                                Shell                            <\/button>\r\n                        <\/li>\r\n\r\n                    \r\n                            \r\n        <\/ul>\r\n        <div class=\"code-snippet__tab-content tab-content\">\r\n\r\n                            \r\n                    <div class=\"tab-pane show active\" id=\"code_0\" role=\"tabpanel\" aria-labelledby=\"code_0\">\r\n                        <div class=\"code-snippet__code-wrapper\">\r\n                            <div class=\"nav nav-buttons code-snippet__copy-wrapper\">\r\n                                <button type=\"button\" class=\"nav-link code-snippet__copy\" data-clipboard-target=\"#code_0_content\" data-clipboard-label=\"Copiar para a \u00e1rea de transfer\u00eancia\" data-clipboard-success-label=\"Copiado!\" data-clipboard-error-label=\"N\u00e3o foi poss\u00edvel copiar\" data-tooltip=\"Copiar para a \u00e1rea de transfer\u00eancia\" data-tooltip-placement=\"left\" role=\"tooltip\" aria-label=\"Copiar para a \u00e1rea de transfer\u00eancia\">\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--default\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/file-stack.svg\"><\/svg>\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--success\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/badge-check.svg\"><\/svg>\r\n                                <\/button>\r\n                            <\/div>\r\n                            <pre class=\"w-100 h-100 m-0 line-numbers language-shell\">\r\n                                <code id=\"code_0_content\" class=\"language-shell\">\r\n\r\n                                    import base64\r\nimport hashlib\r\nimport hmac\r\nimport time\r\n\r\nMAX_CALLBACK_AGE_SECONDS = 300\r\n\r\n@router.post(&quot;\/sinch\/callbacks&quot;)\r\nasync def handle_callback(request: Request) -&gt; Response:\r\n    raw_body = await request.body()\r\n    signature = request.headers.get(&quot;x-sinch-webhook-signature&quot;, &quot;&quot;)\r\n    nonce = request.headers.get(&quot;x-sinch-webhook-signature-nonce&quot;, &quot;&quot;)\r\n    timestamp = request.headers.get(&quot;x-sinch-webhook-signature-timestamp&quot;, &quot;&quot;)\r\n\r\n    try:\r\n        age = abs(time.time() - int(timestamp))\r\n    except ValueError:\r\n        raise HTTPException(status_code=401, detail=&quot;Bad timestamp&quot;)\r\n    if age &gt; MAX_CALLBACK_AGE_SECONDS:\r\n        raise HTTPException(status_code=401, detail=&quot;Stale callback&quot;)\r\n\r\n    signed_data = raw_body + b&quot;.&quot; + nonce.encode() + b&quot;.&quot; + timestamp.encode()\r\n    expected = base64.b64encode(\r\n        hmac.new(\r\n            settings.webhook_secret.encode(), signed_data, hashlib.sha256\r\n        ).digest()\r\n    ).decode()\r\n    if not hmac.compare_digest(expected, signature):\r\n        raise HTTPException(status_code=401, detail=&quot;Invalid signature&quot;)\r\n\r\n    payload = json.loads(raw_body)\r\n    if &quot;message_delivery_report&quot; in payload:\r\n        await record_delivery(payload[&quot;message_delivery_report&quot;])\r\n    elif &quot;message&quot; in payload:\r\n        await record_inbound_message(payload[&quot;message&quot;])\r\n    return Response(status_code=200)\r\n                                <\/code>\r\n                            <\/pre>\r\n                        <\/div>\r\n                    <\/div>\r\n                                    <\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Voc\u00ea precisa acertar algumas coisas aqui. A string assinada \u00e9 o corpo bruto, o nonce e o carimbo de data\/hora\u00a0unidos por pontos, nessa ordem, e o hash (digest) \u00e9 em base64 em vez de hex. Calcule isso sobre os bytes\u00a0brutos, e n\u00e3o sobre um dicion\u00e1rio resserializado, caso contr\u00e1rio nunca haver\u00e1 correspond\u00eancia. A chave \u00e9 o segredo que voc\u00ea definiu ao\u00a0criar o webhook, e <code>x-sinch-webhook-signature-algorithm<\/code> informa qual algoritmo foi usado\u00a0(atualmente, <code>HmacSHA256<\/code>).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Apenas uma assinatura v\u00e1lida n\u00e3o indica que a solicita\u00e7\u00e3o \u00e9 nova. Qualquer pessoa que capturar um callback\u00a0assinado poder\u00e1 envi\u00e1-lo novamente e o HMAC ainda far\u00e1 a valida\u00e7\u00e3o, e \u00e9 para isso que o carimbo de data\/hora e o nonce\u00a0servem: a Sinch descreve o nonce como \u00fanico por callback exatamente para esse fim. A\u00a0janela de cinco minutos acima \u00e9 uma escolha minha, e n\u00e3o um valor documentado. Al\u00e9m disso, essa \u00e9 a metade barata do processo. Se\u00a0precisar de um rigoroso processamento de uso \u00fanico, armazene em cache os nonces que voc\u00ea j\u00e1 aceitou pela dura\u00e7\u00e3o desse\u00a0intervalo e rejeite repeti\u00e7\u00f5es. Tamb\u00e9m vale a pena contar com o tratamento idempotente, pois a Sinch tenta novamente com\u00a0recuo (backoff) exponencial e voc\u00ea legitimamente ver\u00e1 a mesma confirma\u00e7\u00e3o de entrega duas vezes, mas a idempot\u00eancia n\u00e3o \u00e9 uma\u00a0prote\u00e7\u00e3o contra repeti\u00e7\u00e3o: ela impede que um callback reproduzido corrompa seu estado sem nunca avisar voc\u00ea\u00a0que ele foi repetido.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Adi\u00e7\u00e3o de RCS<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Com o caminho de envio independente de canal, o RCS \u00e9 um tipo de mensagem e uma propriedade do canal:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-sand\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p class=\"mb-0\">Fa\u00e7a o upgrade do lembrete para um rich card de RCS com o remetente verificado do RockBank, um resumo do pagamento e um bot\u00e3o \u201cPagar agora\u201d que abra nossa p\u00e1gina de pagamento hospedada. Mantenha o fallback de SMS para os dispositivos que n\u00e3o aceitam RCS.<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">O cart\u00e3o \u00e9 uma <code>card_message<\/code> com a escolha <code>url_message<\/code>, entregue para a mesma <code>_send<\/code> de\u00a0antes:<\/p>\n\n\n    <div data-content-type=\"longform\" class=\"code-snippet longform-spacings rounded-lg overflow-hidden shadow \" data-count=\"1\">\r\n        <ul class=\"nav nav-buttons code-snippet__tabs longform-except position-relative d-flex gap-2 flex-wrap p-3\" role=\"tablist\">\r\n\r\n                            \r\n                    \r\n                        <li class=\"nav-item\" role=\"presentation\">\r\n                            <button class=\"nav-link active\" data-bs-toggle=\"tab\" data-bs-target=\"#code_0\" type=\"button\" role=\"tab\" aria-controls=\"code_0\" aria-selected=\"true\">\r\n                                Shell                            <\/button>\r\n                        <\/li>\r\n\r\n                    \r\n                            \r\n        <\/ul>\r\n        <div class=\"code-snippet__tab-content tab-content\">\r\n\r\n                            \r\n                    <div class=\"tab-pane show active\" id=\"code_0\" role=\"tabpanel\" aria-labelledby=\"code_0\">\r\n                        <div class=\"code-snippet__code-wrapper\">\r\n                            <div class=\"nav nav-buttons code-snippet__copy-wrapper\">\r\n                                <button type=\"button\" class=\"nav-link code-snippet__copy\" data-clipboard-target=\"#code_0_content\" data-clipboard-label=\"Copiar para a \u00e1rea de transfer\u00eancia\" data-clipboard-success-label=\"Copiado!\" data-clipboard-error-label=\"N\u00e3o foi poss\u00edvel copiar\" data-tooltip=\"Copiar para a \u00e1rea de transfer\u00eancia\" data-tooltip-placement=\"left\" role=\"tooltip\" aria-label=\"Copiar para a \u00e1rea de transfer\u00eancia\">\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--default\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/file-stack.svg\"><\/svg>\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--success\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/badge-check.svg\"><\/svg>\r\n                                <\/button>\r\n                            <\/div>\r\n                            <pre class=\"w-100 h-100 m-0 line-numbers language-shell\">\r\n                                <code id=\"code_0_content\" class=\"language-shell\">\r\n\r\n                                        async def send_reminder_card(    \r\n        self,    \r\n        to: str,    \r\n        due_date: date,    \r\n        amount: Decimal,    \r\n        account_last4: str,    \r\n        payment_url: str,    \r\n    ) -&gt; dict[str, Any]:    \r\n        card: dict[str, Any] = {    \r\n            &quot;title&quot;: f&quot;Payment due {due_date:%d %b}&quot;,    \r\n            &quot;description&quot;: f&quot;Amount: ${amount:,.2f}. Account ending {account_last4}.&quot;,    \r\n            &quot;choices&quot;: [    \r\n                {&quot;url_message&quot;: {&quot;title&quot;: &quot;Pay now&quot;, &quot;url&quot;: payment_url}}    \r\n            ],    \r\n        }    \r\n        return await self._send(    \r\n            to,    \r\n            {&quot;card_message&quot;: card},    \r\n            channel_properties={&quot;RCS_WEBVIEW_MODE&quot;: &quot;TALL&quot;},    \r\n        )   \r\n                                <\/code>\r\n                            <\/pre>\r\n                        <\/div>\r\n                    <\/div>\r\n                                    <\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\"><code>channel_properties<\/code> \u00e9 um \u00fanico flat map. Portanto, a chave RCS e o <code>SMS_SENDER<\/code> de que o fallback precisa acabam\u00a0no mesmo dicion\u00e1rio. Essa fus\u00e3o \u00e9 o motivo pelo qual <code>_send<\/code> usa as propriedades como argumento em vez de\u00a0constru\u00ed-las em linha. <code>RCS_WEBVIEW_MODE<\/code> controla o quanto da tela a p\u00e1gina de pagamento ocupar\u00e1 quando\u00a0abrir: <code>FULL<\/code>, <code>HALF<\/code> ou <code>TALL<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dois detalhes sobre o RCS em que \u00e9 f\u00e1cil errar:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>O RCS n\u00e3o processa o pagamento.<\/strong> O bot\u00e3o \u201cPagar agora\u201d \u00e9 uma a\u00e7\u00e3o de URL. Ele abre a sua p\u00e1gina de pagamento\u00a0hospedada em uma visualiza\u00e7\u00e3o web sobre a thread, o cliente paga l\u00e1 e o controle volta para a\u00a0conversa. O que o RCS fornece \u00e9 o remetente verificado, o rich card com identidade de marca, e um cliente que nunca\u00a0sai de seu app de mensagens. O pagamento ainda passa pelo seu provedor de pagamentos.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Falhas de fallback chegam tarde.<\/strong> Cada canal que voc\u00ea nomeia em <code>channel_priority_order<\/code> precisa ser\u00a0configurado no app ou a solicita\u00e7\u00e3o \u00e9 totalmente rejeitada, que \u00e9 o caso f\u00e1cil de depurar. O\u00a0caso mais dif\u00edcil \u00e9 o de um canal configurado que falha na entrega: <code>messages:send<\/code> retorna 200, e o\u00a0resultado aparece depois como um callback de <code>MESSAGE_DELIVERY<\/code>, com <code>SWITCHING_CHANNEL<\/code> marcando o ponto\u00a0em que o fallback entrou em a\u00e7\u00e3o. Esse \u00e9 outro motivo para acertar nos gatilhos de webhook antes de confiar no caminho de\u00a0fallback.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Voz, verifica\u00e7\u00e3o e e-mail<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">As quatro pe\u00e7as restantes exigiram um prompt cada.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Voz.<\/strong> Se o lembrete ficar sem resposta e o pagamento continuar pendente, o RockBank faz uma chamada. Um\u00a0an\u00fancio de texto para fala n\u00e3o precisa de nenhum servidor de fluxo de chamada, s\u00f3 de um POST para a API de voz:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-sand\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p class=\"mb-0\">Adicione um lembrete de voz para os pagamentos que ainda estiverem pendentes dois dias ap\u00f3s o envio da mensagem. Use um an\u00fancio de texto para fala por meio da API de voz da Sinch usando nossas credenciais do aplicativo de voz, e defina o identificador de chamadas com nosso n\u00famero registrado.<\/p><\/div>\r\n    <\/div>\r\n\n\n    <div data-content-type=\"longform\" class=\"code-snippet longform-spacings rounded-lg overflow-hidden shadow \" data-count=\"1\">\r\n        <ul class=\"nav nav-buttons code-snippet__tabs longform-except position-relative d-flex gap-2 flex-wrap p-3\" role=\"tablist\">\r\n\r\n                            \r\n                    \r\n                        <li class=\"nav-item\" role=\"presentation\">\r\n                            <button class=\"nav-link active\" data-bs-toggle=\"tab\" data-bs-target=\"#code_0\" type=\"button\" role=\"tab\" aria-controls=\"code_0\" aria-selected=\"true\">\r\n                                Shell                            <\/button>\r\n                        <\/li>\r\n\r\n                    \r\n                            \r\n        <\/ul>\r\n        <div class=\"code-snippet__tab-content tab-content\">\r\n\r\n                            \r\n                    <div class=\"tab-pane show active\" id=\"code_0\" role=\"tabpanel\" aria-labelledby=\"code_0\">\r\n                        <div class=\"code-snippet__code-wrapper\">\r\n                            <div class=\"nav nav-buttons code-snippet__copy-wrapper\">\r\n                                <button type=\"button\" class=\"nav-link code-snippet__copy\" data-clipboard-target=\"#code_0_content\" data-clipboard-label=\"Copiar para a \u00e1rea de transfer\u00eancia\" data-clipboard-success-label=\"Copiado!\" data-clipboard-error-label=\"N\u00e3o foi poss\u00edvel copiar\" data-tooltip=\"Copiar para a \u00e1rea de transfer\u00eancia\" data-tooltip-placement=\"left\" role=\"tooltip\" aria-label=\"Copiar para a \u00e1rea de transfer\u00eancia\">\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--default\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/file-stack.svg\"><\/svg>\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--success\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/badge-check.svg\"><\/svg>\r\n                                <\/button>\r\n                            <\/div>\r\n                            <pre class=\"w-100 h-100 m-0 line-numbers language-shell\">\r\n                                <code id=\"code_0_content\" class=\"language-shell\">\r\n\r\n                                    async def place_reminder_call(self, to: str, message: str) -&gt; dict[str, Any]:\r\n    &quot;&quot;&quot;Place a text-to-speech reminder call via the Voice API.&quot;&quot;&quot;\r\n    tts: dict[str, Any] = {\r\n        &quot;destination&quot;: {&quot;type&quot;: &quot;number&quot;, &quot;endpoint&quot;: to},\r\n        &quot;cli&quot;: self.caller_id,\r\n        &quot;locale&quot;: &quot;en-US&quot;,\r\n        &quot;text&quot;: message,\r\n    }\r\n    response = await self.client.post(\r\n        &quot;\/calling\/v1\/callouts&quot;,\r\n        auth=(self.voice_app_key, self.voice_app_secret),\r\n        json={&quot;method&quot;: &quot;ttsCallout&quot;, &quot;ttsCallout&quot;: tts},\r\n    )\r\n    if response.is_error:\r\n        raise SinchClientError(\r\n            f&quot;Voice callout failed ({response.status_code}): {response.text}&quot;\r\n        )\r\n    return response.json()\r\n                                <\/code>\r\n                            <\/pre>\r\n                        <\/div>\r\n                    <\/div>\r\n                                    <\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Aten\u00e7\u00e3o \u00e0 diferen\u00e7a de autentica\u00e7\u00e3o: a voz usa uma chave e segredo de aplicativo, e n\u00e3o as credenciais em n\u00edvel de projeto\u00a0que a Conversation API usa. Produto diferente, esquema de autentica\u00e7\u00e3o diferente, e as Skills\u00a0abrangem os dois. Portanto, o prompt n\u00e3o precisou explicar nenhum deles.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">O <code>destination<\/code> de uma chamada telef\u00f4nica \u00e9 <code>{\"type\": \"number\", \"endpoint\": \"+46...\"}<\/code> no formato E.164, e a\u00a0resposta fornece um <code>callId<\/code>. A refer\u00eancia da API lista <code>cli<\/code> como opcional, mas mesmo assim, defina o campo com um n\u00famero\u00a0verificado ou atribu\u00eddo por meio do seu painel: sem um identificador de chamadas utiliz\u00e1vel, voc\u00ea poder\u00e1 receber o ID\u00a0de uma chamada que sequer chegar\u00e1 ao telefone.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Verifica\u00e7\u00e3o.<\/strong> Uma senha de uso \u00fanico antes que a p\u00e1gina de pagamento aceite qualquer coisa:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-sand\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p class=\"mb-0\">Antes da p\u00e1gina de pagamento aceitar qualquer coisa, fa\u00e7a a verifica\u00e7\u00e3o do cliente com uma senha de uso \u00fanico por SMS, pela API de verifica\u00e7\u00e3o da Sinch. Exponha o m\u00e9todo para podermos altern\u00e1-lo de acordo com a solicita\u00e7\u00e3o.<\/p><\/div>\r\n    <\/div>\r\n\n\n    <div data-content-type=\"longform\" class=\"code-snippet longform-spacings rounded-lg overflow-hidden shadow \" data-count=\"1\">\r\n        <ul class=\"nav nav-buttons code-snippet__tabs longform-except position-relative d-flex gap-2 flex-wrap p-3\" role=\"tablist\">\r\n\r\n                            \r\n                    \r\n                        <li class=\"nav-item\" role=\"presentation\">\r\n                            <button class=\"nav-link active\" data-bs-toggle=\"tab\" data-bs-target=\"#code_0\" type=\"button\" role=\"tab\" aria-controls=\"code_0\" aria-selected=\"true\">\r\n                                Shell                            <\/button>\r\n                        <\/li>\r\n\r\n                    \r\n                            \r\n        <\/ul>\r\n        <div class=\"code-snippet__tab-content tab-content\">\r\n\r\n                            \r\n                    <div class=\"tab-pane show active\" id=\"code_0\" role=\"tabpanel\" aria-labelledby=\"code_0\">\r\n                        <div class=\"code-snippet__code-wrapper\">\r\n                            <div class=\"nav nav-buttons code-snippet__copy-wrapper\">\r\n                                <button type=\"button\" class=\"nav-link code-snippet__copy\" data-clipboard-target=\"#code_0_content\" data-clipboard-label=\"Copiar para a \u00e1rea de transfer\u00eancia\" data-clipboard-success-label=\"Copiado!\" data-clipboard-error-label=\"N\u00e3o foi poss\u00edvel copiar\" data-tooltip=\"Copiar para a \u00e1rea de transfer\u00eancia\" data-tooltip-placement=\"left\" role=\"tooltip\" aria-label=\"Copiar para a \u00e1rea de transfer\u00eancia\">\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--default\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/file-stack.svg\"><\/svg>\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--success\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/badge-check.svg\"><\/svg>\r\n                                <\/button>\r\n                            <\/div>\r\n                            <pre class=\"w-100 h-100 m-0 line-numbers language-shell\">\r\n                                <code id=\"code_0_content\" class=\"language-shell\">\r\n\r\n                                    async def start(self, phone: str, method: str = &quot;sms&quot;) -&gt; dict[str, Any]:\r\n    response = await self.client.post(\r\n        &quot;\/verification\/v1\/verifications&quot;,\r\n        auth=self._verification_auth,\r\n        json={&quot;identity&quot;: {&quot;type&quot;: &quot;number&quot;, &quot;endpoint&quot;: phone}, &quot;method&quot;: method},\r\n    )\r\n    ...\r\n\r\nasync def verify(self, reference: str, code: str) -&gt; dict[str, Any]:\r\n    path = f&quot;\/verification\/v1\/verifications\/id\/{quote(reference, safe=&#039;&#039;)}&quot;\r\n    response = await self.client.put(\r\n        path,\r\n        auth=self._verification_auth,\r\n        json={&quot;method&quot;: &quot;sms&quot;, &quot;sms&quot;: {&quot;code&quot;: code}},\r\n    )\r\n    ...\r\n                                <\/code>\r\n                            <\/pre>\r\n                        <\/div>\r\n                    <\/div>\r\n                                    <\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\"><code>_verification_auth<\/code> \u00e9 um terceiro conjunto de credenciais: a verifica\u00e7\u00e3o autentica com a chave e o\u00a0segredo de seu pr\u00f3prio app no painel. Portanto, n\u00e3o se trata das chaves em n\u00edvel de projeto que a Conversation API\u00a0usa, tampouco as chaves do aplicativo de voz.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>start<\/code> retorna um <code>id<\/code>, e \u00e9 nele que voc\u00ea informa o c\u00f3digo. Os valores de m\u00e9todos s\u00e3o a parte que\u00a0vale a pena conhecer: <code>sms<\/code> para a senha via mensagem, <code>callout<\/code> para que ela seja lida em uma chamada de voz, al\u00e9m de\u00a0<code>flashcall<\/code>, <code>seamless<\/code> e <code>whatsapp<\/code>. O corpo do relat\u00f3rio \u00e9 parametrizado pelo mesmo m\u00e9todo, ou seja,\u00a0<code>{\"method\": \"sms\", \"sms\": {\"code\": \"1234\"}}<\/code> para a mensagem e\u00a0<code>{\"method\": \"callout\", \"callout\": {\"code\": \"1234\"}}<\/code> para a chamada.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As verifica\u00e7\u00f5es expiram em alguns minutos. Sendo assim, encare um erro 400 no relat\u00f3rio como &#8220;inicie um novo&#8221; em vez de encarar\u00a0como uma tentativa fracassada. A mudan\u00e7a de um cliente para <code>callout<\/code> quando a mensagem n\u00e3o chega corresponde a uma segunda chamada\u00a0<code>start<\/code> com um m\u00e9todo diferente. Portanto, a decis\u00e3o de quando oferec\u00ea-la faz parte da l\u00f3gica do aplicativo: quanto tempo\u00a0voc\u00ea aguarda, quantas tentativas voc\u00ea permite e se o cliente pede ou voc\u00ea toma essa decis\u00e3o por ele.\u00a0Vale a pena planejar isso de forma deliberada em vez de deixar ao encargo de um loop de repeti\u00e7\u00e3o.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>E-mail.<\/strong> A confirma\u00e7\u00e3o \u00e9 enviada pelo Mailgun, que est\u00e1 no mesmo plugin:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-sand\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p class=\"mb-0\">Quando um pagamento ou plano de pagamento for confirmado, envie um e-mail de confirma\u00e7\u00e3o pelo Mailgun com o valor, a data e um n\u00famero de refer\u00eancia.<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">O Mailgun exige um quarto conjunto de credenciais e \u00e9 diferente: sua autentica\u00e7\u00e3o ocorre por dom\u00ednio e chave, n\u00e3o por app, como as outras tr\u00eas. Trata-se de uma autentica\u00e7\u00e3o b\u00e1sica em <code>POST \/v3\/{domain}\/messages<\/code>, com <code>api<\/code> como nome de usu\u00e1rio e uma chave de API como senha. O <code>o:testmode=yes<\/code> aceita envios sem que haja a entrega, algo que vale a pena saber na hora de integrar as confirma\u00e7\u00f5es.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Pegadinhas (Gotchas)<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Inicie o registro do 10DLC antes de escrever o c\u00f3digo de envio.<\/strong> Ele \u00e9 necess\u00e1rio para enviar SMS para os n\u00fameros dos EUA; al\u00e9m disso, a aprova\u00e7\u00e3o leva dias, e nenhuma velocidade do agente vai encurtar esse tempo. Esse \u00e9 o motivo mais comum\u00a0para que o projeto de uma tarde acabe levando at\u00e9 a semana seguinte.<\/li>\n\n\n\n<li><strong>A incompatibilidade de regi\u00f5es pode parecer um problema de autentica\u00e7\u00e3o.<\/strong> Todas as URLs da Conversation API s\u00e3o espec\u00edficas de cada regi\u00e3o. Se\u00a0<code>CONVERSATION_REGION<\/code> n\u00e3o corresponder ao local em que o app est\u00e1 hospedado, voc\u00ea receber\u00e1 um erro 404 ou um que indica um problema nas\u00a0suas credenciais. Confira a regi\u00e3o no painel antes de gerar outra chave que seria perfeitamente boa.<\/li>\n\n\n\n<li><strong>Teste o caminho de fallback da mesma forma que testaria o caminho feliz.<\/strong> A disponibilidade do RCS varia de acordo com o mercado, a operadora\u00a0e o dispositivo, e o seu pr\u00f3prio telefone comprova apenas o caminho feliz. Envie para um dispositivo sem RCS e confirme\u00a0se o SMS chega de fato. O <code>messages:transcode<\/code> gera uma pr\u00e9-visualiza\u00e7\u00e3o de como um cart\u00e3o degrada sem envi\u00e1-lo.<\/li>\n\n\n\n<li><strong>O servidor MCP executa a\u00e7\u00f5es reais em uma conta real.<\/strong> Esse \u00e9 o seu objetivo, e isso significa\u00a0apontar para um n\u00famero de teste em vez de uma lista, al\u00e9m de pensar em quais credenciais est\u00e3o\u00a0no perfil do seu shell.<\/li>\n\n\n\n<li><strong>Mantenha o agente na raiz do projeto.<\/strong> Durante uma das execu\u00e7\u00f5es, o agente pesquisou fora da\u00a0pasta aberta, encontrou uma implementa\u00e7\u00e3o mais antiga da mesma funcionalidade num diret\u00f3rio irm\u00e3o e a espelhou\u00a0em vez de usar as Skills. O c\u00f3digo funcionou. Mas ele tamb\u00e9m estava errado. Abra o projeto no qual deseja\u00a0trabalhar.<\/li>\n\n\n\n<li><strong>Leia o que o agente escreveu.<\/strong> A import\u00e2ncia de um agente que comp\u00f5e o c\u00f3digo real da integra\u00e7\u00e3o em vez de\u00a0fornecer um resultado pouco transparente \u00e9 que o c\u00f3digo fica logo ali para voc\u00ea examinar. A lacuna no gatilho de webhook\u00a0e a aus\u00eancia do identificador de chamadas estavam vis\u00edveis no diff.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Qual foi o resultado final<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Pelo lado do cliente: um lembrete chega tr\u00eas dias antes da data de vencimento do pagamento. Em um dispositivo com\u00a0suporte para RCS, ser\u00e1 exibido um rich card com identidade de marca partindo de um remetente verificado com um bot\u00e3o \u201cPagar agora\u201d. Ele toca no bot\u00e3o, faz a verifica\u00e7\u00e3o\u00a0com uma senha, efetua o pagamento sem sair da thread e recebe um e-mail de confirma\u00e7\u00e3o. Se o pagamento\u00a0continuar pendente, a jornada prossegue com um lembrete de voz automatizado.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">At\u00e9 o fim, a jornada perpassa o envio de mensagens, a verifica\u00e7\u00e3o, a voz e o e-mail; todos concebidos por meio do mesmo\u00a0fluxo de trabalho do Cursor. O tempo que o agente me poupou teria sido gasto com a leitura da documenta\u00e7\u00e3o: ou seja, na compreens\u00e3o dos formatos de payload e\u00a0dos esquemas de autentica\u00e7\u00e3o de quatro produtos, al\u00e9m dos ciclos de implementa\u00e7\u00e3o e verifica\u00e7\u00e3o que costumam ocorrer entre um\u00a0payload estar errado e eu descobrir. Digitar nunca foi a parte mais demorada.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Experimente<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Instale o plugin pelo <a href=\"https:\/\/cursor.com\/marketplace\/sinch\/sinch-cursor-plugin\" target=\"_blank\" rel=\"noreferrer noopener\">marketplace do Cursor<\/a>,\u00a0exporte as credenciais, reinicie o Cursor e forne\u00e7a ao agente o prompt da Conversation API logo acima\u00a0usando um n\u00famero de teste. O plugin tem c\u00f3digo aberto em\u00a0<a href=\"https:\/\/github.com\/sinch\/sinch-plugins\" target=\"_blank\" rel=\"noreferrer noopener\">sinch-plugins no GitHub<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Recursos adicionais<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/cursor.com\/marketplace\/sinch\/sinch-cursor-plugin\" target=\"_blank\" rel=\"noreferrer noopener\">Plugin da Sinch para o Cursor no marketplace do Cursor<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/sinch\/sinch-plugins\" target=\"_blank\" rel=\"noreferrer noopener\">sinch-plugins no GitHub<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developers.sinch.com\/docs\/conversation\/\" target=\"_blank\" rel=\"noreferrer noopener\">Documenta\u00e7\u00e3o da Sinch Conversation API<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developers.sinch.com\/docs\/voice\/\" target=\"_blank\" rel=\"noreferrer noopener\">Documenta\u00e7\u00e3o da API de voz da Sinch<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developers.sinch.com\/docs\/verification\/\" target=\"_blank\" rel=\"noreferrer noopener\">Documenta\u00e7\u00e3o da API de verifica\u00e7\u00e3o da Sinch<\/a><\/li>\n<\/ul>\n","protected":false},"author":93,"featured_media":210360,"menu_order":0,"template":"","meta":{"_acf_changed":false,"footnotes":""},"tags":[],"blog_category":[309],"class_list":["post-211311","blog","type-blog","status-publish","has-post-thumbnail","hentry","blog_category-desenvolvedores"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Como construir um stack de comunica\u00e7\u00f5es no Cursor usando a Sinch<\/title>\n<meta name=\"description\" content=\"Como criei um stack de comunica\u00e7\u00e3o com o cliente no Cursor com a ajuda do plugin da Sinch, desde lembretes por SMS e RCS at\u00e9 voz, verifica\u00e7\u00e3o e e-mail.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/sinch.com\/pt\/blog\/como-construi-stack-comunicacoes-cursor-plugin-sinch\/\" \/>\n<meta property=\"og:locale\" content=\"pt_BR\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Como construir um stack de comunica\u00e7\u00f5es no Cursor usando a Sinch\" \/>\n<meta property=\"og:description\" content=\"Como criei um stack de comunica\u00e7\u00e3o com o cliente no Cursor com a ajuda do plugin da Sinch, desde lembretes por SMS e RCS at\u00e9 voz, verifica\u00e7\u00e3o e e-mail.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/sinch.com\/pt\/blog\/como-construi-stack-comunicacoes-cursor-plugin-sinch\/\" \/>\n<meta property=\"og:site_name\" content=\"Sinch\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/wearesinch\/\" \/>\n<meta property=\"og:image\" content=\"https:\/\/sinch.com\/wp-content\/uploads\/2026\/09\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1296\" \/>\n\t<meta property=\"og:image:height\" content=\"788\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:site\" content=\"@WeAreSinch\" \/>\n<meta name=\"twitter:label1\" content=\"Est. tempo de leitura\" \/>\n\t<meta name=\"twitter:data1\" content=\"11 minutos\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"BlogPosting\",\"@id\":\"https:\\\/\\\/sinch.com\\\/pt\\\/blog\\\/como-construi-stack-comunicacoes-cursor-plugin-sinch\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/pt\\\/blog\\\/como-construi-stack-comunicacoes-cursor-plugin-sinch\\\/\"},\"author\":{\"name\":\"Gunnar Grosch\",\"@id\":\"https:\\\/\\\/sinch.com\\\/pt\\\/#\\\/schema\\\/person\\\/b2bde1c2728a21bc953ea0e129936c08\"},\"headline\":\"Como constru\u00ed uma stack de comunica\u00e7\u00f5es no Cursor com o plugin da Sinch\",\"datePublished\":\"2026-09-25T10:24:16+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/pt\\\/blog\\\/como-construi-stack-comunicacoes-cursor-plugin-sinch\\\/\"},\"wordCount\":3032,\"publisher\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/pt\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/pt\\\/blog\\\/como-construi-stack-comunicacoes-cursor-plugin-sinch\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/sinch.com\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png\",\"inLanguage\":\"pt-BR\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/sinch.com\\\/pt\\\/blog\\\/como-construi-stack-comunicacoes-cursor-plugin-sinch\\\/\",\"url\":\"https:\\\/\\\/sinch.com\\\/pt\\\/blog\\\/como-construi-stack-comunicacoes-cursor-plugin-sinch\\\/\",\"name\":\"Como construir um stack de comunica\u00e7\u00f5es no Cursor usando a Sinch\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/pt\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/pt\\\/blog\\\/como-construi-stack-comunicacoes-cursor-plugin-sinch\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/pt\\\/blog\\\/como-construi-stack-comunicacoes-cursor-plugin-sinch\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/sinch.com\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png\",\"datePublished\":\"2026-09-25T10:24:16+00:00\",\"description\":\"Como criei um stack de comunica\u00e7\u00e3o com o cliente no Cursor com a ajuda do plugin da Sinch, desde lembretes por SMS e RCS at\u00e9 voz, verifica\u00e7\u00e3o e e-mail.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/pt\\\/blog\\\/como-construi-stack-comunicacoes-cursor-plugin-sinch\\\/#breadcrumb\"},\"inLanguage\":\"pt-BR\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/sinch.com\\\/pt\\\/blog\\\/como-construi-stack-comunicacoes-cursor-plugin-sinch\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"pt-BR\",\"@id\":\"https:\\\/\\\/sinch.com\\\/pt\\\/blog\\\/como-construi-stack-comunicacoes-cursor-plugin-sinch\\\/#primaryimage\",\"url\":\"https:\\\/\\\/sinch.com\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png\",\"contentUrl\":\"https:\\\/\\\/sinch.com\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png\",\"width\":1296,\"height\":788},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/sinch.com\\\/pt\\\/blog\\\/como-construi-stack-comunicacoes-cursor-plugin-sinch\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/sinch.com\\\/pt\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Blog\",\"item\":\"https:\\\/\\\/sinch.com\\\/pt\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Como constru\u00ed uma stack de comunica\u00e7\u00f5es no Cursor com o plugin da Sinch\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/sinch.com\\\/pt\\\/#website\",\"url\":\"https:\\\/\\\/sinch.com\\\/pt\\\/\",\"name\":\"Sinch\",\"description\":\"Envio de mensagens SMS, Voz, E-mail, V\u00eddeo & APIs de verifica\u00e7\u00e3o | Sinch\",\"publisher\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/pt\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/sinch.com\\\/pt\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"pt-BR\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/sinch.com\\\/pt\\\/#organization\",\"name\":\"Sinch\",\"url\":\"https:\\\/\\\/sinch.com\\\/pt\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"pt-BR\",\"@id\":\"https:\\\/\\\/sinch.com\\\/pt\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/sinch.com\\\/wp-content\\\/uploads\\\/2024\\\/09\\\/Thumbnail-Logo-Honey.png\",\"contentUrl\":\"https:\\\/\\\/sinch.com\\\/wp-content\\\/uploads\\\/2024\\\/09\\\/Thumbnail-Logo-Honey.png\",\"width\":800,\"height\":496,\"caption\":\"Sinch\"},\"image\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/pt\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/wearesinch\\\/\",\"https:\\\/\\\/x.com\\\/WeAreSinch\",\"https:\\\/\\\/www.linkedin.com\\\/company\\\/sinch\",\"https:\\\/\\\/www.youtube.com\\\/channel\\\/UCZZ2u_B2afTxA0v-xcgfsaw\",\"https:\\\/\\\/www.instagram.com\\\/wearesinch\\\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/sinch.com\\\/pt\\\/#\\\/schema\\\/person\\\/b2bde1c2728a21bc953ea0e129936c08\",\"name\":\"Gunnar Grosch\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"pt-BR\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/8f177695c01082754db3f0858d534fdaceedba31f74932d7de39991e5e9d7e8d?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/8f177695c01082754db3f0858d534fdaceedba31f74932d7de39991e5e9d7e8d?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/8f177695c01082754db3f0858d534fdaceedba31f74932d7de39991e5e9d7e8d?s=96&d=mm&r=g\",\"caption\":\"Gunnar Grosch\"},\"description\":\"Gunnar Grosch is a Principal Developer Advocate at Sinch and an engineer with over 25 years of experience building software, scaling developer communities, and making complex technology accessible. He shares code, insights, and lessons through conference talks, blog posts, and open source.\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Como construir um stack de comunica\u00e7\u00f5es no Cursor usando a Sinch","description":"Como criei um stack de comunica\u00e7\u00e3o com o cliente no Cursor com a ajuda do plugin da Sinch, desde lembretes por SMS e RCS at\u00e9 voz, verifica\u00e7\u00e3o e e-mail.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/sinch.com\/pt\/blog\/como-construi-stack-comunicacoes-cursor-plugin-sinch\/","og_locale":"pt_BR","og_type":"article","og_title":"Como construir um stack de comunica\u00e7\u00f5es no Cursor usando a Sinch","og_description":"Como criei um stack de comunica\u00e7\u00e3o com o cliente no Cursor com a ajuda do plugin da Sinch, desde lembretes por SMS e RCS at\u00e9 voz, verifica\u00e7\u00e3o e e-mail.","og_url":"https:\/\/sinch.com\/pt\/blog\/como-construi-stack-comunicacoes-cursor-plugin-sinch\/","og_site_name":"Sinch","article_publisher":"https:\/\/www.facebook.com\/wearesinch\/","og_image":[{"width":1296,"height":788,"url":"https:\/\/sinch.com\/wp-content\/uploads\/2026\/09\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png","type":"image\/png"}],"twitter_card":"summary_large_image","twitter_site":"@WeAreSinch","twitter_misc":{"Est. tempo de leitura":"11 minutos"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"BlogPosting","@id":"https:\/\/sinch.com\/pt\/blog\/como-construi-stack-comunicacoes-cursor-plugin-sinch\/#article","isPartOf":{"@id":"https:\/\/sinch.com\/pt\/blog\/como-construi-stack-comunicacoes-cursor-plugin-sinch\/"},"author":{"name":"Gunnar Grosch","@id":"https:\/\/sinch.com\/pt\/#\/schema\/person\/b2bde1c2728a21bc953ea0e129936c08"},"headline":"Como constru\u00ed uma stack de comunica\u00e7\u00f5es no Cursor com o plugin da Sinch","datePublished":"2026-09-25T10:24:16+00:00","mainEntityOfPage":{"@id":"https:\/\/sinch.com\/pt\/blog\/como-construi-stack-comunicacoes-cursor-plugin-sinch\/"},"wordCount":3032,"publisher":{"@id":"https:\/\/sinch.com\/pt\/#organization"},"image":{"@id":"https:\/\/sinch.com\/pt\/blog\/como-construi-stack-comunicacoes-cursor-plugin-sinch\/#primaryimage"},"thumbnailUrl":"https:\/\/sinch.com\/wp-content\/uploads\/2026\/09\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png","inLanguage":"pt-BR"},{"@type":"WebPage","@id":"https:\/\/sinch.com\/pt\/blog\/como-construi-stack-comunicacoes-cursor-plugin-sinch\/","url":"https:\/\/sinch.com\/pt\/blog\/como-construi-stack-comunicacoes-cursor-plugin-sinch\/","name":"Como construir um stack de comunica\u00e7\u00f5es no Cursor usando a Sinch","isPartOf":{"@id":"https:\/\/sinch.com\/pt\/#website"},"primaryImageOfPage":{"@id":"https:\/\/sinch.com\/pt\/blog\/como-construi-stack-comunicacoes-cursor-plugin-sinch\/#primaryimage"},"image":{"@id":"https:\/\/sinch.com\/pt\/blog\/como-construi-stack-comunicacoes-cursor-plugin-sinch\/#primaryimage"},"thumbnailUrl":"https:\/\/sinch.com\/wp-content\/uploads\/2026\/09\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png","datePublished":"2026-09-25T10:24:16+00:00","description":"Como criei um stack de comunica\u00e7\u00e3o com o cliente no Cursor com a ajuda do plugin da Sinch, desde lembretes por SMS e RCS at\u00e9 voz, verifica\u00e7\u00e3o e e-mail.","breadcrumb":{"@id":"https:\/\/sinch.com\/pt\/blog\/como-construi-stack-comunicacoes-cursor-plugin-sinch\/#breadcrumb"},"inLanguage":"pt-BR","potentialAction":[{"@type":"ReadAction","target":["https:\/\/sinch.com\/pt\/blog\/como-construi-stack-comunicacoes-cursor-plugin-sinch\/"]}]},{"@type":"ImageObject","inLanguage":"pt-BR","@id":"https:\/\/sinch.com\/pt\/blog\/como-construi-stack-comunicacoes-cursor-plugin-sinch\/#primaryimage","url":"https:\/\/sinch.com\/wp-content\/uploads\/2026\/09\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png","contentUrl":"https:\/\/sinch.com\/wp-content\/uploads\/2026\/09\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png","width":1296,"height":788},{"@type":"BreadcrumbList","@id":"https:\/\/sinch.com\/pt\/blog\/como-construi-stack-comunicacoes-cursor-plugin-sinch\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/sinch.com\/pt\/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https:\/\/sinch.com\/pt\/blog\/"},{"@type":"ListItem","position":3,"name":"Como constru\u00ed uma stack de comunica\u00e7\u00f5es no Cursor com o plugin da Sinch"}]},{"@type":"WebSite","@id":"https:\/\/sinch.com\/pt\/#website","url":"https:\/\/sinch.com\/pt\/","name":"Sinch","description":"Envio de mensagens SMS, Voz, E-mail, V\u00eddeo & APIs de verifica\u00e7\u00e3o | Sinch","publisher":{"@id":"https:\/\/sinch.com\/pt\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/sinch.com\/pt\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"pt-BR"},{"@type":"Organization","@id":"https:\/\/sinch.com\/pt\/#organization","name":"Sinch","url":"https:\/\/sinch.com\/pt\/","logo":{"@type":"ImageObject","inLanguage":"pt-BR","@id":"https:\/\/sinch.com\/pt\/#\/schema\/logo\/image\/","url":"https:\/\/sinch.com\/wp-content\/uploads\/2024\/09\/Thumbnail-Logo-Honey.png","contentUrl":"https:\/\/sinch.com\/wp-content\/uploads\/2024\/09\/Thumbnail-Logo-Honey.png","width":800,"height":496,"caption":"Sinch"},"image":{"@id":"https:\/\/sinch.com\/pt\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/wearesinch\/","https:\/\/x.com\/WeAreSinch","https:\/\/www.linkedin.com\/company\/sinch","https:\/\/www.youtube.com\/channel\/UCZZ2u_B2afTxA0v-xcgfsaw","https:\/\/www.instagram.com\/wearesinch\/"]},{"@type":"Person","@id":"https:\/\/sinch.com\/pt\/#\/schema\/person\/b2bde1c2728a21bc953ea0e129936c08","name":"Gunnar Grosch","image":{"@type":"ImageObject","inLanguage":"pt-BR","@id":"https:\/\/secure.gravatar.com\/avatar\/8f177695c01082754db3f0858d534fdaceedba31f74932d7de39991e5e9d7e8d?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/8f177695c01082754db3f0858d534fdaceedba31f74932d7de39991e5e9d7e8d?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/8f177695c01082754db3f0858d534fdaceedba31f74932d7de39991e5e9d7e8d?s=96&d=mm&r=g","caption":"Gunnar Grosch"},"description":"Gunnar Grosch is a Principal Developer Advocate at Sinch and an engineer with over 25 years of experience building software, scaling developer communities, and making complex technology accessible. He shares code, insights, and lessons through conference talks, blog posts, and open source."}]}},"_links":{"self":[{"href":"https:\/\/sinch.com\/pt\/wp-json\/wp\/v2\/blog\/211311","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sinch.com\/pt\/wp-json\/wp\/v2\/blog"}],"about":[{"href":"https:\/\/sinch.com\/pt\/wp-json\/wp\/v2\/types\/blog"}],"author":[{"embeddable":true,"href":"https:\/\/sinch.com\/pt\/wp-json\/wp\/v2\/users\/93"}],"version-history":[{"count":0,"href":"https:\/\/sinch.com\/pt\/wp-json\/wp\/v2\/blog\/211311\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sinch.com\/pt\/wp-json\/wp\/v2\/media\/210360"}],"wp:attachment":[{"href":"https:\/\/sinch.com\/pt\/wp-json\/wp\/v2\/media?parent=211311"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sinch.com\/pt\/wp-json\/wp\/v2\/tags?post=211311"},{"taxonomy":"blog_category","embeddable":true,"href":"https:\/\/sinch.com\/pt\/wp-json\/wp\/v2\/blog_category?post=211311"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}