{"id":211307,"date":"2026-09-25T10:24:09","date_gmt":"2026-09-25T10:24:09","guid":{"rendered":"https:\/\/sinch.com\/blog\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\/"},"modified":"2026-09-25T10:24:09","modified_gmt":"2026-09-25T10:24:09","slug":"wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe","status":"publish","type":"blog","link":"https:\/\/sinch.com\/de\/blog\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\/","title":{"rendered":"Wie ich mit dem Sinch-Plugin einen Kommunikations-Stack in Cursor gebaut habe"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Eine Kommunikations-Integration ist selten wegen des eigentlichen Versands langsam. Die meiste Zeit entf\u00e4llt auf Nutzdaten-Strukturen, ein\u00a0anderes Authentifizierungsschema f\u00fcr jedes Produkt und die Kluft zwischen etwas, das in der Staging-Umgebung funktioniert, und\u00a0etwas, das auf dem Smartphone in Ihrer Hand funktioniert.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ich habe genug davon mit den Sinch-APIs gebaut, um zu wissen, wo diese Zeit bleibt, und genau das macht das\u00a0<a href=\"https:\/\/cursor.com\/marketplace\/sinch\/sinch-cursor-plugin\" target=\"_blank\" rel=\"noreferrer noopener\">Sinch-Plugin f\u00fcr Cursor<\/a> lohnt es sich,\u00a0es richtig zu testen: Ich kann beurteilen, ob der Code, den ein Agent zur\u00fcckgibt, richtig ist. Also nahm ich mir einen Nachmittag\u00a0Zeit und eine Demo-Bank als Grundlage.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">RockBank ist diese Demo-Bank: ein fiktiver Kreditgeber, dessen Kunden per Post versendete Zahlungserinnerungen ignorieren.\u00a0Die Aufgabe bestand darin, die Erinnerungen in die Kan\u00e4le zu verlagern, die die Leute tats\u00e4chlich lesen, und das gesamte\u00a0Messaging hinter einem Sendepfad und Webhook-Endpunkt zu belassen, sodass das Hinzuf\u00fcgen eines Kanals eine Konfigurations\u00e4nderung\u00a0anstelle einer Neuschreibung ist.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Funktionsweise des Flows<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Eine SMS-Zahlungserinnerung, die \u00fcber die Sinch Conversation API versendet wird<\/li>\n\n\n\n<li>Dieselbe Erinnerung mit einem Upgrade auf RCS, einem verifizierten Absender und einer Schaltfl\u00e4che &#8222;Jetzt bezahlen&#8220; im Thread<\/li>\n\n\n\n<li>Eine automatisierte Voice-Erinnerung, falls die Zahlung noch aussteht<\/li>\n\n\n\n<li>Identit\u00e4tsverifizierung mit einem Einmalpasswort, bevor die Zahlung erfolgt<\/li>\n\n\n\n<li>Eine E-Mail-Best\u00e4tigung \u00fcber Mailgun, sobald die Zahlung abgeschlossen ist<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Das ist Messaging, Voice, Verifizierung und E-Mail hinter einem Sendepfad und eingehenden Webhook.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Voraussetzungen f\u00fcr den Build<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Der Code von RockBank ist nicht ver\u00f6ffentlicht, daher ist dies kein Clone-and-Run. Im Folgenden wird die Kontoeinrichtung beschrieben,\u00a0die die Snippets voraussetzen, und dieselbe Einrichtung, die Sie ben\u00f6tigen w\u00fcrden, um Ihre eigene Version zu bauen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Cursor mit installiertem Sinch-Plugin und eine Test-Telefonnummer, die Ihnen geh\u00f6rt<\/li>\n\n\n\n<li>Eine Conversation API-App: Projekt-ID, Schl\u00fcssel-ID, Key Secret und App-ID<\/li>\n\n\n\n<li>Separate Zugangsdaten pro Produkt: ein Voice-Anwendungsschl\u00fcssel und -Secret mit einer zugewiesenen Nummer\u00a0sowie ein App-Schl\u00fcssel und -Secret f\u00fcr die Verifizierung, was wiederum eine eigene App ist<\/li>\n\n\n\n<li>Ein f\u00fcr Ihren Markt zugelassenes RCS-Profil, bei dem SMS in derselben App als Fallback konfiguriert ist. Die Zulassung durch den Mobilfunkanbieter\u00a0nimmt Zeit in Anspruch, beginnen Sie also fr\u00fchzeitig damit. Ein SMS-Absender ist kein Ersatz: Ohne zugelassenes Profil\u00a0landet jede Erinnerung als Klartext.<\/li>\n\n\n\n<li><a href=\"https:\/\/developers.sinch.com\/docs\/10dlc-registration\/\" target=\"_blank\" rel=\"noreferrer noopener\">10DLC-Registrierung<\/a> im Gange, falls Sie an\u00a0US-Nummern versenden. Mehr dazu unter Fallstricke.<\/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-7628\"><p class=\"h5 m-0\" id=\"toc-title-7628\">Inhaltsverzeichnis<\/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=\"#funktionsweise-des-flows\">Funktionsweise des Flows<\/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=\"#voraussetzungen-fuer-den-build\">Voraussetzungen f\u00fcr den Build<\/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=\"#plugin-einrichten\">Plugin einrichten<\/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=\"#den-sendepfad-auf-der-conversation-api-aufbauen\">Den Sendepfad auf der Conversation API aufbauen<\/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=\"#testen-im-editor\">Testen im 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=\"#eingehenden-webhook-anbinden\">Eingehenden Webhook anbinden<\/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=\"#rcs-hinzufuegen\">RCS hinzuf\u00fcgen<\/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=\"#voice-verifizierung-und-e-mail\">Voice, Verifizierung und 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=\"#fallstricke\">Fallstricke<\/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=\"#mein-endergebnis\">Mein Endergebnis<\/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=\"#probieren-sie-es-selbst-aus\">Probieren Sie es selbst aus<\/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=\"#zusaetzliche-ressourcen\">Zus\u00e4tzliche Ressourcen<\/a><\/div><\/div><\/nav>\n\n\n<h2 class=\"wp-block-heading\">Plugin einrichten<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Installieren Sie es \u00fcber den Cursor Marketplace oder \u00fcber die Befehlspalette:<\/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\">Das Plugin besteht aus drei Teilen, die unterschiedliche Aufgaben erf\u00fcllen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Skills<\/strong> laden Sinch-API-Wissen in den Kontext des Agenten, bevor dieser Code schreibt: Endpunkte, Authentifizierungsschemata, Formen der Nutzdaten, Kanaleigenschaften. Dies ist der Teil, der dar\u00fcber entscheidet, ob der generierte Code f\u00fcr die echte API oder f\u00fcr eine plausibel aussehende Erfindung kompiliert wird.<\/li>\n\n\n\n<li><strong>Befehle<\/strong> (wie <code>\/send-message<\/code> und <code>\/list-webhooks<\/code>) rufen Sinch-APIs direkt aus dem Chat auf, ohne dass Code dazwischenliegt.<\/li>\n\n\n\n<li><strong>Die MCP-Server<\/strong>, und zwar zwei davon. <code>sinch<\/code> f\u00fchrt <code>npx -y @sinch\/mcp<\/code> lokal aus und verbindet den Agenten mit Ihrem Live-Konto, damit er Nachrichten senden und die Konfiguration \u00fcberpr\u00fcfen kann, w\u00e4hrend Sie arbeiten. <code>sinch-docs<\/code> ist remote unter <code>developers.sinch.com\/mcp<\/code> verf\u00fcgbar, um die Dokumentation zu durchsuchen. Nur der erste ben\u00f6tigt Zugangsdaten.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Zugangsdaten sind der einzige Teil der Einrichtung, bei dem man pr\u00e4zise vorgehen sollte. F\u00fcnf Variablen kommen in einen <code>env<\/code>-Block in <code>~\/.cursor\/settings.json<\/code>, und Cursor ben\u00f6tigt einen Neustart, um sie zu \u00fcbernehmen:<\/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=\"In die Zwischenablage kopieren\" data-clipboard-success-label=\"Kopiert\" data-clipboard-error-label=\"Kopieren nicht m\u00f6glich\" data-tooltip=\"In die Zwischenablage kopieren\" data-tooltip-placement=\"left\" role=\"tooltip\" aria-label=\"In die Zwischenablage kopieren\">\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\">Falls der Server weiterhin fehlende Zugangsdaten meldet, exportieren Sie dieselben f\u00fcnf Variablen in Ihrem Shell-Profil\u00a0und f\u00fchren Sie ein vollst\u00e4ndiges Beenden und einen Neustart durch: Es wird als <code>npx -y @sinch\/mcp<\/code> ausgef\u00fchrt und erbt die Umgebung\u00a0der Shell, von der aus Cursor gestartet wurde.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Diese f\u00fcnf geh\u00f6ren zum MCP-Server und decken die Conversation API ab, sodass der Voice-Schl\u00fcssel und das Secret\u00a0niemals in Cursor gelangen. Telefonanrufe finden nur statt, wenn Ihr eigener Code ausgef\u00fchrt wird.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Best\u00e4tigen Sie die Verbindung, bevor Sie etwas schreiben:<\/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=&#8220;RockBank test&#8220;<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Derselbe Befehl nimmt eine Fallback-Kette entgegen, was eine n\u00fctzliche Vorschau darauf bietet, was der Sendepfad\u00a0sp\u00e4ter tun wird:<\/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=&#8220;RockBank test&#8220; &#8211;fallback=RCS,SMS<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<h2 class=\"wp-block-heading\">Den Sendepfad auf der Conversation API aufbauen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Die Struktur des Sendepfads ist die Entscheidung, von der alles andere abh\u00e4ngt. Die Conversation API nimmt\u00a0einen generischen Nachrichtenrumpf und transkodiert ihn pro Kanal, und sie behandelt die Kanalauswahl und das Fallback\u00a0als Daten in der Anfrage anstatt als Verzweigungen in Ihrem Code. F\u00fcr RockBank ist das das gesamte Design:\u00a0Die Erinnerung wird \u00fcber RCS mit SMS dahinter gesendet, Voice kommt sp\u00e4ter hinzu, und der Aufrufort erf\u00e4hrt\u00a0nie etwas davon.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Es lohnt sich, dies in den Prompt aufzunehmen, anstatt es der Schlussfolgerung zu \u00fcberlassen. Bitten Sie einen Agenten, \u201eder Kundschaft zu schreiben, falls eine Zahlung f\u00e4llig ist\u201c, und Sie erhalten vern\u00fcnftigerweise einen Sendepfad, der um einen Kanal herum aufgebaut ist,\u00a0denn genau das haben Sie verlangt. Geben Sie stattdessen die Intention an und \u00fcberlassen Sie den Mechanismus dem 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\">Implementieren Sie den Sendepfad f\u00fcr Erinnerungen \u00fcber die Sinch Conversation API. Senden Sie RCS mit SMS als Fallback und halten Sie die Schnittstelle stabil, sodass das sp\u00e4tere Hinzuf\u00fcgen eines Kanals keine Aufruforte \u00e4ndert. Verarbeiten Sie au\u00dferdem Zustellbest\u00e4tigungen und protokollieren Sie diese.<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Keine Feldnamen, kein Endpunkt, kein Authentifizierungsschema. Der <code>conversation-api<\/code>-Skill gibt bereits vor, wie das Fallback\u00a0funktioniert: F\u00fcgen Sie ein <code>channel_priority_order<\/code>-Array hinzu und listen Sie jede Kanalidentit\u00e4t beim Empf\u00e4nger auf. Diese\u00a0Aufteilung ist diejenige, die Sie in Ihren eigenen Prompts anstreben sollten. Sie beschreiben das gew\u00fcnschte Verhalten und die\u00a0Einschr\u00e4nkungen, an die es sich halten muss, und der Skill liefert die Nutzdaten.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dieser letzte Nebensatz leistet echte Arbeit. Ohne ihn erhalten Sie einen Sendepfad ohne Beobachtbarkeit und finden\u00a0erst heraus, wie es l\u00e4uft, falls jemand aus der Kundschaft mitteilt, dass die Erinnerung nie angekommen ist. Damit muss der Agent\u00a0ber\u00fccksichtigen, was passiert, nachdem <code>messages:send<\/code> eine 200 zur\u00fcckgibt, denn dort befinden sich die interessanten\u00a0Fehler.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Was zur\u00fcckkam, ist eine private Methode, die jeden Nachrichtenrumpf der Conversation API aufnimmt, wobei das Fallback\u00a0als <code>channel_priority_order<\/code> und beide Kanalidentit\u00e4ten beim Empf\u00e4nger ausgedr\u00fcckt werden:<\/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=\"In die Zwischenablage kopieren\" data-clipboard-success-label=\"Kopiert\" data-clipboard-error-label=\"Kopieren nicht m\u00f6glich\" data-tooltip=\"In die Zwischenablage kopieren\" data-tooltip-placement=\"left\" role=\"tooltip\" aria-label=\"In die Zwischenablage kopieren\">\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\">Jeder Kanal nach diesem \u00e4ndert den <code>message<\/code>-Rumpf, den er an <code>_send<\/code> \u00fcbergibt, und sonst nichts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die Kanaleigenschaft <code>SMS_SENDER<\/code> ist vorhanden, da SMS einen Absender ben\u00f6tigt, sofern die App keinen\u00a0Standardabsender festgelegt hat. Das kam ebenfalls vom Skill.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Testen im Editor<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Daf\u00fcr ist der MCP-Server da. Nachdem der Client geschrieben war und die Datei noch offen war, bat ich\u00a0um einen echten Versand:<\/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\">Senden Sie eine Testerinnerung \u00fcber diesen Dienst an meine Nummer, mit einem F\u00e4lligkeitsdatum in drei Tagen und einem Betrag von 240\u00a0$.<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Eine Live-Nachricht \u00fcber ein Live-Konto, direkt aus dem 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=\"In die Zwischenablage kopieren\" data-clipboard-success-label=\"Kopiert\" data-clipboard-error-label=\"Kopieren nicht m\u00f6glich\" data-tooltip=\"In die Zwischenablage kopieren\" data-tooltip-placement=\"left\" role=\"tooltip\" aria-label=\"In die Zwischenablage kopieren\">\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\">Diese Antwort bedeutet \u201eakzeptiert\u201c, nicht \u201ezugestellt\u201c. Die Zustellung erfolgt asynchron und wird auf dem\u00a0Webhook angezeigt. Der n\u00e4chste Schritt besteht also darin sicherzustellen, dass der Webhook ihn auch tats\u00e4chlich abonniert hat.<br><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Eingehenden Webhook anbinden<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ein Endpunkt verarbeitet beide Verkehrsrichtungen: <code>MESSAGE_DELIVERY<\/code> f\u00fcr Empfangsbest\u00e4tigungen und\u00a0<code>MESSAGE_INBOUND<\/code> f\u00fcr Antworten der Kundschaft. Registrieren Sie ihn mit einem Befehl:<\/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\">Das Ziel muss HTTPS sein und aus dem Internet erreichbar sein. Richten Sie f\u00fcr die lokale Arbeit also einen Tunnel vor\u00a0Ihrem Handler ein und registrieren Sie die Tunnel-URL. Das Secret ist in der API optional, aber \u00fcbergeben Sie eines: Es ist das,\u00a0was die Callbacks signiert, es muss mit dem Wert \u00fcbereinstimmen, gegen den Ihr Handler verifiziert, und ohne dieses gibt es keine\u00a0Signatur zur \u00dcberpr\u00fcfung. Eine App unterst\u00fctzt bis zu f\u00fcnf Webhooks, und das erneute Registrieren desselben Ziels\u00a0gibt eine 400 zur\u00fcck.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pr\u00fcfen Sie dann, was die App tats\u00e4chlich abonniert hat, denn das ist nicht immer das, worum Sie gebeten haben:<\/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\">Es lohnt sich, dies zu \u00fcberpr\u00fcfen, bevor Sie nachgelagerten Systemen vertrauen. Ein Webhook, der nur mit <code>MESSAGE_INBOUND<\/code> registriert wurde,\u00a0akzeptiert Ihre Registrierung problemlos, und Ihr Handler f\u00fcr die Zustellbest\u00e4tigung bleibt ungenutzt und wird nie ausgef\u00fchrt.\u00a0Der Sendepfad sieht fehlerfrei aus und das Zustellungsprotokoll bleibt leer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Der Handler selbst ben\u00f6tigt eine Signatur\u00fcberpr\u00fcfung, bevor er sich den Rumpf ansieht:<\/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\">Schreiben Sie den FastAPI-Handler f\u00fcr den <code>\/sinch\/callbacks<\/code>-Endpunkt. Verifizieren Sie die HMAC-SHA256-Signatur, die Sinch bei jedem Callback sendet, bevor Sie den Rumpf verarbeiten, lehnen Sie Anfragen mit einem veralteten oder wiederverwendeten Zeitstempel ab und routen Sie Zustellbest\u00e4tigungen sowie eingehende Nachrichten an separate Handler.<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Sinch signiert <a href=\"https:\/\/developers.sinch.com\/docs\/conversation\/callbacks\" target=\"_blank\" rel=\"noreferrer noopener\">Callbacks<\/a> mit HMAC-SHA256\u00a0\u00fcber den rohen Rumpf, die Nonce und den Zeitstempel:<\/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=\"In die Zwischenablage kopieren\" data-clipboard-success-label=\"Kopiert\" data-clipboard-error-label=\"Kopieren nicht m\u00f6glich\" data-tooltip=\"In die Zwischenablage kopieren\" data-tooltip-placement=\"left\" role=\"tooltip\" aria-label=\"In die Zwischenablage kopieren\">\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\">Hierbei gilt es einige Dinge zu beachten. Die signierte Zeichenfolge besteht aus dem rohen Rumpf, der Nonce und dem Zeitstempel,\u00a0die in dieser Reihenfolge durch Punkte verbunden sind, und der Digest ist Base64 statt Hex. Berechnen Sie es \u00fcber die rohen\u00a0Bytes, nicht \u00fcber ein neu serialisiertes Dict, sonst wird es niemals \u00fcbereinstimmen. Der Schl\u00fcssel ist das Secret, das Sie festgelegt haben, als Sie\u00a0den Webhook erstellt haben, und <code>x-sinch-webhook-signature-algorithm<\/code> teilt Ihnen mit, welcher Algorithmus verwendet wurde\u00a0(aktuell <code>HmacSHA256<\/code>).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Eine g\u00fcltige Signatur allein besagt nicht, dass die Anfrage aktuell ist. Jeder, der einen signierten\u00a0Callback abf\u00e4ngt, kann ihn erneut senden, und der HMAC ist weiterhin g\u00fcltig. Genau daf\u00fcr sind der Zeitstempel und die Nonce\u00a0da: Sinch beschreibt die Nonce aus genau diesem Grund als einzigartig pro Callback. Das obige Zeitfenster von\u00a0f\u00fcnf Minuten ist meine eigene Wahl, kein dokumentierter Wert, und es ist der einfachere Teil. Falls Sie\u00a0eine strikte einmalige Verarbeitung ben\u00f6tigen, cachen Sie die bereits akzeptierten Nonces f\u00fcr die Dauer dieses\u00a0Zeitfensters und lehnen Sie Wiederholungen ab. Eine idempotente Handhabung ist ebenfalls sinnvoll, da Sinch erneute Versuche mit\u00a0exponentiellem Backoff durchf\u00fchrt und Sie legitimerweise dieselbe Best\u00e4tigung zweimal sehen werden. Idempotenz ist jedoch kein\u00a0Replay-Schutz: Sie verhindert, dass ein wiederholter Callback Ihren Status besch\u00e4digt, ohne Ihnen jemals mitzuteilen,\u00a0dass er wiederholt wurde.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">RCS hinzuf\u00fcgen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Da der Sendepfad bereits kanalunabh\u00e4ngig ist, ist RCS ein Nachrichtentyp und eine Kanaleigenschaft:<\/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\">F\u00fchren Sie f\u00fcr die Erinnerung ein Upgrade auf eine RCS-Rich Card mit dem verifizierten Absender der RockBank, einer Zahlungs\u00fcbersicht und einer Schaltfl\u00e4che \u201cJetzt bezahlen\u201d durch, die unsere gehostete Zahlungsseite \u00f6ffnet. Behalten Sie das SMS-Fallback f\u00fcr Ger\u00e4te bei, die kein RCS unterst\u00fctzen.<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Die Card ist eine <code>card_message<\/code> mit einer <code>url_message<\/code>-Auswahl darauf, die an dasselbe <code>_send<\/code> wie\u00a0zuvor \u00fcbergeben wird:<\/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=\"In die Zwischenablage kopieren\" data-clipboard-success-label=\"Kopiert\" data-clipboard-error-label=\"Kopieren nicht m\u00f6glich\" data-tooltip=\"In die Zwischenablage kopieren\" data-tooltip-placement=\"left\" role=\"tooltip\" aria-label=\"In die Zwischenablage kopieren\">\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> ist eine einzige flache Map, sodass der RCS-Schl\u00fcssel und der <code>SMS_SENDER<\/code>, den das Fallback ben\u00f6tigt, im\u00a0selben Dictionary landen. Diese Zusammenf\u00fchrung ist der Grund, warum <code>_send<\/code> die Eigenschaften als Argument annimmt, anstatt\u00a0sie inline zu erstellen. <code>RCS_WEBVIEW_MODE<\/code> steuert, wie viel vom Bildschirm die Zahlungsseite einnimmt, wenn\u00a0sie ge\u00f6ffnet wird: <code>FULL<\/code>, <code>HALF<\/code> oder <code>TALL<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Zwei Details zu RCS, die man leicht falsch machen kann:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>RCS verarbeitet die Zahlung nicht.<\/strong> Die Schaltfl\u00e4che \u201cJetzt bezahlen\u201d ist eine URL-Aktion. Sie \u00f6ffnet Ihre gehostete\u00a0Zahlungsseite in einer Webansicht \u00fcber dem Thread, die Kundschaft bezahlt dort und die Steuerung kehrt zur\u00a0Konversation zur\u00fcck. Was RCS Ihnen bietet, ist der verifizierte Absender, die markenkonforme Card und eine Kundschaft, die niemals\u00a0ihre Messaging-App verl\u00e4sst. Die Zahlung l\u00e4uft weiterhin \u00fcber Ihren Zahlungsanbieter.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fallback-Fehler kommen sp\u00e4t an.<\/strong> Jeder Kanal, den Sie in <code>channel_priority_order<\/code> angeben, muss\u00a0in der App konfiguriert sein, andernfalls wird die Anfrage direkt abgelehnt, was der einfache Fall beim Debuggen ist. Der\u00a0schwierigere Fall ist ein konfigurierter Kanal, dessen Zustellung fehlschl\u00e4gt: <code>messages:send<\/code> gibt 200 zur\u00fcck, und das\u00a0Ergebnis taucht sp\u00e4ter als <code>MESSAGE_DELIVERY<\/code>-Callback auf, wobei <code>SWITCHING_CHANNEL<\/code> den Punkt markiert,\u00a0an dem das Fallback eingesetzt hat. Ein weiterer Grund, die Webhook-Trigger richtig einzurichten, bevor man sich auf den\u00a0Fallback-Pfad verl\u00e4sst.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Voice, Verifizierung und E-Mail<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr die restlichen vier Teile war jeweils ein Prompt erforderlich.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Voice.<\/strong> Falls die Erinnerung unbeantwortet bleibt und die Zahlung weiterhin aussteht, ruft die RockBank an. Ein\u00a0Text-to-Speech-Anruf erfordert keinen Call-Flow-Server, sondern nur einen POST an die Voice-API:<\/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\">F\u00fcgen Sie eine Voice-Erinnerung f\u00fcr Zahlungen hinzu, die zwei Tage, nachdem die Nachricht versendet wurde, noch ausstehen. Verwenden Sie einen Text-to-Speech-Anruf \u00fcber die Sinch Voice-API mit den Zugangsdaten f\u00fcr unsere Voice-Anwendung und setzen Sie die Anrufer-ID auf unsere registrierte Nummer.<\/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=\"In die Zwischenablage kopieren\" data-clipboard-success-label=\"Kopiert\" data-clipboard-error-label=\"Kopieren nicht m\u00f6glich\" data-tooltip=\"In die Zwischenablage kopieren\" data-tooltip-placement=\"left\" role=\"tooltip\" aria-label=\"In die Zwischenablage kopieren\">\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\">Beachten Sie den Unterschied bei der Authentifizierung: Voice verwendet einen Anwendungsschl\u00fcssel und ein Secret, nicht die Zugangsdaten auf Projektebene,\u00a0welche die Conversation API nutzt. Anderes Produkt, anderes Authentifizierungsschema, und die Skills\u00a0decken beides ab, sodass der Prompt keines von beiden erkl\u00e4ren musste.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>destination<\/code> f\u00fcr einen Telefonanruf lautet <code>{\"type\": \"number\", \"endpoint\": \"+46...\"}<\/code> in E.164, und die\u00a0Antwort liefert Ihnen eine <code>callId<\/code>. Die API-Referenz f\u00fchrt <code>cli<\/code> als optional auf, aber setzen Sie es dennoch auf eine\u00a0verifizierte Nummer oder eine aus Ihrem Dashboard zugewiesene: Ohne eine verwendbare Anrufer-ID k\u00f6nnten Sie eine Call-ID f\u00fcr einen Anruf zur\u00fcckerhalten,\u00a0der das Telefon niemals erreicht.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Verifizierung.<\/strong> Ein Einmalpasswort, bevor die Zahlungsseite irgendetwas akzeptiert:<\/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\">Bevor die Zahlungsseite irgendetwas akzeptiert, verifizieren Sie die Kundschaft mit einem Einmalpasswort per SMS \u00fcber die Sinch Verifizierungs-API. Machen Sie die Methode zug\u00e4nglich, damit wir sie pro Anfrage wechseln k\u00f6nnen.<\/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=\"In die Zwischenablage kopieren\" data-clipboard-success-label=\"Kopiert\" data-clipboard-error-label=\"Kopieren nicht m\u00f6glich\" data-tooltip=\"In die Zwischenablage kopieren\" data-tooltip-placement=\"left\" role=\"tooltip\" aria-label=\"In die Zwischenablage kopieren\">\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> ist ein drittes Set von Zugangsdaten: Die Verifizierung authentifiziert sich mit dem Schl\u00fcssel und\u00a0dem Secret ihrer eigenen App im Dashboard, es sind also weder die Schl\u00fcssel auf Projektebene, welche die Conversation API\u00a0verwendet, noch die f\u00fcr die Voice-Anwendung.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>start<\/code> gibt eine <code>id<\/code> zur\u00fcck, und das ist die ID, f\u00fcr die Sie den Code melden. Die Methodenwerte sind der Teil,\u00a0den man kennen sollte: <code>sms<\/code> f\u00fcr ein Passwort per Nachricht, <code>callout<\/code> f\u00fcr eines, das bei einem Telefonanruf vorgelesen wird, plus\u00a0<code>flashcall<\/code>, <code>seamless<\/code> und <code>whatsapp<\/code>. Der Rumpf des Berichts erh\u00e4lt dieselbe Methode als Schl\u00fcssel, also\u00a0<code>{\"method\": \"sms\", \"sms\": {\"code\": \"1234\"}}<\/code> f\u00fcr die Nachricht und\u00a0<code>{\"method\": \"callout\", \"callout\": {\"code\": \"1234\"}}<\/code> f\u00fcr den Anruf.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Verifizierungen laufen nach wenigen Minuten ab. Behandeln Sie daher eine 400 beim Bericht eher als \u201eeine neue starten\u201c anstatt\u00a0als fehlgeschlagenen Versuch. Eine Person aus der Kundschaft auf <code>callout<\/code> umzustellen, falls die Nachricht nicht ankommt, ist ein zweiter\u00a0<code>start<\/code>-Aufruf mit einer anderen Methode. Die Entscheidung, wann dies angeboten wird, ist Anwendungslogik: Wie lange\u00a0Sie warten, wie viele Versuche Sie zulassen und ob die Kundschaft danach fragt oder Sie f\u00fcr sie entscheiden.\u00a0Es lohnt sich, dies bewusst zu entwerfen, anstatt es einer Wiederholungsschleife zu \u00fcberlassen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>E-Mail.<\/strong> Die Best\u00e4tigung wird \u00fcber Mailgun versendet, das sich im selben Plugin befindet:<\/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\">Falls eine Zahlung oder ein Zahlungs-Abonnement best\u00e4tigt wird, senden Sie eine Best\u00e4tigungs-E-Mail \u00fcber Mailgun mit dem Betrag, dem Datum und einer Referenznummer.<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Mailgun ist ein viertes Set von Zugangsdaten und der Au\u00dfenseiter: Es authentifiziert sich per Domain und Schl\u00fcssel anstatt per App, wie die anderen drei. Das ist eine Basic-Authentifizierung gegen <code>POST \/v3\/{domain}\/messages<\/code>, mit <code>api<\/code> als Benutzername und einem API-Schl\u00fcssel als Passwort. <code>o:testmode=yes<\/code> akzeptiert einen Sendevorgang, ohne ihn zuzustellen, was wissenswert ist, w\u00e4hrend Sie die Best\u00e4tigung anbinden.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Fallstricke<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Starten Sie die 10DLC-Registrierung, bevor Sie den Code f\u00fcr den Versand schreiben.<\/strong> Der Versand von SMS an US-Nummern erfordert dies. Die Genehmigung dauert Tage, und keine Agenten-Geschwindigkeit der Welt kann das abk\u00fcrzen. Das ist der h\u00e4ufigste Grund,\u00a0warum aus einem Nachmittags-Build ein Build f\u00fcr n\u00e4chste Woche wird.<\/li>\n\n\n\n<li><strong>Eine falsche Region liest sich wie ein Authentifizierungsproblem.<\/strong> Alle URLs der Conversation API sind regionsspezifisch. Falls\u00a0<code>CONVERSATION_REGION<\/code> nicht mit dem Speicherort der App \u00fcbereinstimmt, erhalten Sie eine 404-Meldung oder einen Fehler, der auf\u00a0Ihre Zugangsdaten hinweist. \u00dcberpr\u00fcfen Sie die Region im Dashboard, bevor Sie einen vollkommen intakten Schl\u00fcssel neu generieren.<\/li>\n\n\n\n<li><strong>Testen Sie den Fallback-Pfad genauso intensiv wie den Happy Path.<\/strong> Die Verf\u00fcgbarkeit von RCS variiert je nach Markt, Mobilfunkanbieter\u00a0und Ger\u00e4t, und Ihr eigenes Telefon belegt lediglich den Happy Path. Senden Sie an ein Ger\u00e4t ohne RCS und best\u00e4tigen Sie, dass die SMS tats\u00e4chlich ankommt. <code>messages:transcode<\/code> bietet eine Vorschau darauf, wie eine Card herabgestuft wird, ohne sie zu versenden.<\/li>\n\n\n\n<li><strong>Der MCP-Server f\u00fchrt echte Aktionen auf einem echten Konto aus.<\/strong> Genau das ist der Sinn der Sache, und es bedeutet,\u00a0ihn auf eine Testnummer statt auf eine Liste auszurichten und dar\u00fcber nachzudenken, welche Zugangsdaten sich\u00a0in Ihrem Shell-Profil befinden.<\/li>\n\n\n\n<li><strong>Belassen Sie den Agenten im Projektstamm.<\/strong> Bei einem Durchlauf suchte der Agent au\u00dferhalb des ge\u00f6ffneten\u00a0Ordners, fand eine \u00e4ltere Implementierung derselben Funktion in einem benachbarten Verzeichnis und spiegelte diese wider,\u00a0anstatt die Skills zu verwenden. Der Code funktionierte. Es war aber auch der falsche Code. \u00d6ffnen Sie das Projekt, in dem Sie\u00a0arbeiten m\u00f6chten.<\/li>\n\n\n\n<li><strong>Lesen Sie, was der Agent geschrieben hat.<\/strong> Der Wert eines Agenten, der echten Integrations-Code schreibt, anstatt\u00a0ein undurchsichtiges Ergebnis zur\u00fcckzugeben, liegt darin, dass der Code direkt zur \u00dcberpr\u00fcfung vorliegt. Die L\u00fccke beim Webhook-Trigger\u00a0und die fehlende Anrufer-ID waren beide im Diff sichtbar.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Mein Endergebnis<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Aufseiten des Kunden: Eine Erinnerung trifft drei Tage vor F\u00e4lligkeit der Zahlung ein. Auf einem Ger\u00e4t, das RCS\u00a0unterst\u00fctzt, ist es eine markenkonforme Card von einem verifizierten Absender mit einer Schaltfl\u00e4che &#8222;Jetzt bezahlen&#8220;. Sie tippen darauf, verifizieren sich\u00a0mit einem Passcode, bezahlen, ohne den Thread zu verlassen, und erhalten eine E-Mail-Best\u00e4tigung. Ist die Zahlung\u00a0weiterhin ausstehend, folgt in der Journey eine automatisierte Voice-Erinnerung.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Am Ende erstreckt sich die Journey \u00fcber Messaging, Verifizierung, Voice und E-Mail, und alles wurde \u00fcber denselben\u00a0Cursor-Workflow erstellt. Die Zeit, die mir der Agent zur\u00fcckgegeben hat, war die Zeit zum Lesen der Dokumentation: das Ausarbeiten der Formen von Nutzdaten und\u00a0Authentifizierungsschemata \u00fcber vier Produkte hinweg sowie die Deploy-and-Check-Zyklen, die normalerweise zwischen falschen Nutzdaten und meiner Feststellung dar\u00fcber liegen. Das Tippen war nie der langsame Teil.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Probieren Sie es selbst aus<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Installieren Sie das Plugin \u00fcber den <a href=\"https:\/\/cursor.com\/marketplace\/sinch\/sinch-cursor-plugin\" target=\"_blank\" rel=\"noreferrer noopener\">Cursor Marketplace<\/a>,\u00a0exportieren Sie die Zugangsdaten, starten Sie Cursor neu und geben Sie dem Agenten den obigen Prompt f\u00fcr die Conversation API\u00a0gegen eine Testnummer. Das Plugin ist Open Source unter\u00a0<a href=\"https:\/\/github.com\/sinch\/sinch-plugins\" target=\"_blank\" rel=\"noreferrer noopener\">sinch-plugins auf GitHub<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Zus\u00e4tzliche Ressourcen<\/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\">Sinch-Cursor-Plugin im Cursor Marketplace<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/sinch\/sinch-plugins\" target=\"_blank\" rel=\"noreferrer noopener\">sinch-plugins auf GitHub<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developers.sinch.com\/docs\/conversation\/\" target=\"_blank\" rel=\"noreferrer noopener\">Dokumentation zur Sinch Conversation API<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developers.sinch.com\/docs\/voice\/\" target=\"_blank\" rel=\"noreferrer noopener\">Dokumentation zur Sinch Voice-API<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developers.sinch.com\/docs\/verification\/\" target=\"_blank\" rel=\"noreferrer noopener\">Dokumentation zur Sinch Verifizierungs-API<\/a><\/li>\n<\/ul>\n","protected":false},"author":93,"featured_media":210360,"menu_order":0,"template":"","meta":{"_acf_changed":false,"footnotes":""},"tags":[],"blog_category":[752],"class_list":["post-211307","blog","type-blog","status-publish","has-post-thumbnail","hentry","blog_category-entwickler"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Einen Communications Stack in Cursor mit Sinch aufbauen<\/title>\n<meta name=\"description\" content=\"Wie ich mit dem Sinch-Plugin einen Stack f\u00fcr Kundenkommunikation in Cursor aufgebaut habe, von RCS- und SMS-Erinnerungen bis hin zu Voice, Verifizierung und 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\/de\/blog\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\/\" \/>\n<meta property=\"og:locale\" content=\"de_DE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Einen Communications Stack in Cursor mit Sinch aufbauen\" \/>\n<meta property=\"og:description\" content=\"Wie ich mit dem Sinch-Plugin einen Stack f\u00fcr Kundenkommunikation in Cursor aufgebaut habe, von RCS- und SMS-Erinnerungen bis hin zu Voice, Verifizierung und E-Mail.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/sinch.com\/de\/blog\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\/\" \/>\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=\"Gesch\u00e4tzte Lesezeit\" \/>\n\t<meta name=\"twitter:data1\" content=\"11 Minuten\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"BlogPosting\",\"@id\":\"https:\\\/\\\/sinch.com\\\/de\\\/blog\\\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/de\\\/blog\\\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\\\/\"},\"author\":{\"name\":\"Gunnar Grosch\",\"@id\":\"https:\\\/\\\/sinch.com\\\/de\\\/#\\\/schema\\\/person\\\/b2bde1c2728a21bc953ea0e129936c08\"},\"headline\":\"Wie ich mit dem Sinch-Plugin einen Kommunikations-Stack in Cursor gebaut habe\",\"datePublished\":\"2026-09-25T10:24:09+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/de\\\/blog\\\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\\\/\"},\"wordCount\":2640,\"publisher\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/de\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/de\\\/blog\\\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/sinch.com\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png\",\"inLanguage\":\"de-DE\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/sinch.com\\\/de\\\/blog\\\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\\\/\",\"url\":\"https:\\\/\\\/sinch.com\\\/de\\\/blog\\\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\\\/\",\"name\":\"Einen Communications Stack in Cursor mit Sinch aufbauen\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/de\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/de\\\/blog\\\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/de\\\/blog\\\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\\\/#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:09+00:00\",\"description\":\"Wie ich mit dem Sinch-Plugin einen Stack f\u00fcr Kundenkommunikation in Cursor aufgebaut habe, von RCS- und SMS-Erinnerungen bis hin zu Voice, Verifizierung und E-Mail.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/de\\\/blog\\\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\\\/#breadcrumb\"},\"inLanguage\":\"de-DE\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/sinch.com\\\/de\\\/blog\\\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"de-DE\",\"@id\":\"https:\\\/\\\/sinch.com\\\/de\\\/blog\\\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\\\/#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\\\/de\\\/blog\\\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/sinch.com\\\/de\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Blog\",\"item\":\"https:\\\/\\\/sinch.com\\\/de\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Wie ich mit dem Sinch-Plugin einen Kommunikations-Stack in Cursor gebaut habe\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/sinch.com\\\/de\\\/#website\",\"url\":\"https:\\\/\\\/sinch.com\\\/de\\\/\",\"name\":\"Sinch\",\"description\":\"SMS Messaging, Voice, E-Mail, Video & Verifizierungs-APIs | Sinch\",\"publisher\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/de\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/sinch.com\\\/de\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"de-DE\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/sinch.com\\\/de\\\/#organization\",\"name\":\"Sinch\",\"url\":\"https:\\\/\\\/sinch.com\\\/de\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de-DE\",\"@id\":\"https:\\\/\\\/sinch.com\\\/de\\\/#\\\/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\\\/de\\\/#\\\/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\\\/de\\\/#\\\/schema\\\/person\\\/b2bde1c2728a21bc953ea0e129936c08\",\"name\":\"Gunnar Grosch\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de-DE\",\"@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":"Einen Communications Stack in Cursor mit Sinch aufbauen","description":"Wie ich mit dem Sinch-Plugin einen Stack f\u00fcr Kundenkommunikation in Cursor aufgebaut habe, von RCS- und SMS-Erinnerungen bis hin zu Voice, Verifizierung und 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\/de\/blog\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\/","og_locale":"de_DE","og_type":"article","og_title":"Einen Communications Stack in Cursor mit Sinch aufbauen","og_description":"Wie ich mit dem Sinch-Plugin einen Stack f\u00fcr Kundenkommunikation in Cursor aufgebaut habe, von RCS- und SMS-Erinnerungen bis hin zu Voice, Verifizierung und E-Mail.","og_url":"https:\/\/sinch.com\/de\/blog\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\/","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":{"Gesch\u00e4tzte Lesezeit":"11 Minuten"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"BlogPosting","@id":"https:\/\/sinch.com\/de\/blog\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\/#article","isPartOf":{"@id":"https:\/\/sinch.com\/de\/blog\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\/"},"author":{"name":"Gunnar Grosch","@id":"https:\/\/sinch.com\/de\/#\/schema\/person\/b2bde1c2728a21bc953ea0e129936c08"},"headline":"Wie ich mit dem Sinch-Plugin einen Kommunikations-Stack in Cursor gebaut habe","datePublished":"2026-09-25T10:24:09+00:00","mainEntityOfPage":{"@id":"https:\/\/sinch.com\/de\/blog\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\/"},"wordCount":2640,"publisher":{"@id":"https:\/\/sinch.com\/de\/#organization"},"image":{"@id":"https:\/\/sinch.com\/de\/blog\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\/#primaryimage"},"thumbnailUrl":"https:\/\/sinch.com\/wp-content\/uploads\/2026\/09\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png","inLanguage":"de-DE"},{"@type":"WebPage","@id":"https:\/\/sinch.com\/de\/blog\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\/","url":"https:\/\/sinch.com\/de\/blog\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\/","name":"Einen Communications Stack in Cursor mit Sinch aufbauen","isPartOf":{"@id":"https:\/\/sinch.com\/de\/#website"},"primaryImageOfPage":{"@id":"https:\/\/sinch.com\/de\/blog\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\/#primaryimage"},"image":{"@id":"https:\/\/sinch.com\/de\/blog\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\/#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:09+00:00","description":"Wie ich mit dem Sinch-Plugin einen Stack f\u00fcr Kundenkommunikation in Cursor aufgebaut habe, von RCS- und SMS-Erinnerungen bis hin zu Voice, Verifizierung und E-Mail.","breadcrumb":{"@id":"https:\/\/sinch.com\/de\/blog\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\/#breadcrumb"},"inLanguage":"de-DE","potentialAction":[{"@type":"ReadAction","target":["https:\/\/sinch.com\/de\/blog\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\/"]}]},{"@type":"ImageObject","inLanguage":"de-DE","@id":"https:\/\/sinch.com\/de\/blog\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\/#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\/de\/blog\/wie-ich-kommunikations-stack-cursor-sinch-plugin-gebaut-habe\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/sinch.com\/de\/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https:\/\/sinch.com\/de\/blog\/"},{"@type":"ListItem","position":3,"name":"Wie ich mit dem Sinch-Plugin einen Kommunikations-Stack in Cursor gebaut habe"}]},{"@type":"WebSite","@id":"https:\/\/sinch.com\/de\/#website","url":"https:\/\/sinch.com\/de\/","name":"Sinch","description":"SMS Messaging, Voice, E-Mail, Video & Verifizierungs-APIs | Sinch","publisher":{"@id":"https:\/\/sinch.com\/de\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/sinch.com\/de\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"de-DE"},{"@type":"Organization","@id":"https:\/\/sinch.com\/de\/#organization","name":"Sinch","url":"https:\/\/sinch.com\/de\/","logo":{"@type":"ImageObject","inLanguage":"de-DE","@id":"https:\/\/sinch.com\/de\/#\/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\/de\/#\/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\/de\/#\/schema\/person\/b2bde1c2728a21bc953ea0e129936c08","name":"Gunnar Grosch","image":{"@type":"ImageObject","inLanguage":"de-DE","@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\/de\/wp-json\/wp\/v2\/blog\/211307","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sinch.com\/de\/wp-json\/wp\/v2\/blog"}],"about":[{"href":"https:\/\/sinch.com\/de\/wp-json\/wp\/v2\/types\/blog"}],"author":[{"embeddable":true,"href":"https:\/\/sinch.com\/de\/wp-json\/wp\/v2\/users\/93"}],"version-history":[{"count":0,"href":"https:\/\/sinch.com\/de\/wp-json\/wp\/v2\/blog\/211307\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sinch.com\/de\/wp-json\/wp\/v2\/media\/210360"}],"wp:attachment":[{"href":"https:\/\/sinch.com\/de\/wp-json\/wp\/v2\/media?parent=211307"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sinch.com\/de\/wp-json\/wp\/v2\/tags?post=211307"},{"taxonomy":"blog_category","embeddable":true,"href":"https:\/\/sinch.com\/de\/wp-json\/wp\/v2\/blog_category?post=211307"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}