Send application event SMS with Cloudflare Workers

Send SMS from application events using a Cloudflare service binding and your TNZ SMS Worker. Test a demo event and connect your own application logic.

Updated

Send an SMS when an event occurs in your Cloudflare application, using your existing TNZ SMS Worker.

Your application selects the events and calls the SMS Worker through a Cloudflare service binding.

Prerequisite: Complete Cloudflare Workers and Sending SMS, including its curl test.

Keep that SMS Worker deployed. Have its service name and matching WORKER_API_KEY ready, and use the same Cloudflare account. This guide adds a second Worker.

  1. Application Worker Trigger demo event
  2. SMS Worker Check and Submit
  3. TNZ REST API Send SMS
  4. Mobile Phone Receives the SMS

This demonstration uses a button to simulate an application event called demo.completed. It does not change or save application data. The application runs locally in Wrangler and calls your deployed SMS Worker.

1. Configure the application Worker

1.1 Create the project

Create a second project alongside tnz-worker:

Terminal
cd <your-project-folder>
npm create cloudflare@latest -- application-event-demo

At the prompts, select:

  1. Hello World example
  2. Worker only
  3. JavaScript
  4. No when asked whether to deploy

1.2 Open the project folder

Run:

Terminal
cd application-event-demo

1.3 Bind the application to your SMS Worker

In application-event-demo/wrangler.jsonc, add this services array inside the existing top-level object. Keep the generated settings and add a comma between properties.

wrangler.jsonc
"services": [
  {
    "binding": "TNZ_SMS",
    "service": "tnz-worker",
    "remote": true
  }
]
  • binding gives the application access to env.TNZ_SMS.
  • service must match the deployed SMS Worker's name in its Wrangler configuration. Change tnz-worker here if you chose another name.
  • remote: true connects local development to that deployed Worker. Sign in to the same Cloudflare account used for the SMS Worker.

The binding selects the destination Worker. The URL in the code below supplies the /send-sms path; sms.internal is a placeholder, not a domain to register.

See Cloudflare's HTTP service bindings and supported development bindings.

1.4 Configure local secrets

Create application-event-demo/.dev.vars with these values:

.dev.vars
WORKER_API_KEY="YOUR_SMS_WORKER_KEY"
DEMO_MOBILE="YOUR_MOBILE_NUMBER"
  1. Replace YOUR_SMS_WORKER_KEY with the matching WORKER_API_KEY from your SMS Worker.
  2. Replace YOUR_MOBILE_NUMBER with your own NZ or AU mobile number. See supported number formats.
  3. Check that .dev.vars* is in application-event-demo/.gitignore.

Keep this file out of source control and screenshots.

The application supplies the Worker key in its server-side Bearer header, including through a service binding. TNZ_AUTH_TOKEN stays in the SMS Worker. Neither key reaches the browser.

2. Add the event handler and demo

Reusable event function and local demo

onApplicationEvent() sends the event message. The remaining handler serves the local demo form.

Replace application-event-demo/src/index.js with this complete demonstration:

src/index.js
View complete JavaScript (70 lines)
const LOCAL_ORIGIN = "http://127.0.0.1:8787";
const headers = { "Cache-Control": "no-store" };

// Call this with an event created by your server-side application logic.
// The demo event does not change or save application data.
async function onApplicationEvent(event, env) {
  return env.TNZ_SMS.fetch("https://sms.internal/send-sms", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${env.WORKER_API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      to: event.mobile,
      message: `Demo: ${event.type} (${event.reference}).`,
      test: false
    })
  });
}

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    // Keep this unauthenticated demonstration on your own computer.
    if (url.origin !== LOCAL_ORIGIN) {
      return new Response("Local demo only", { status: 403, headers });
    }
    if (request.method === "GET" && url.pathname === "/") {
      return new Response(`<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Application event SMS demo</title></head>
<body>
<h1>Application event SMS demo</h1>
<p>This simulates an application event. It does not save application data.</p>
<p>The button sends a live SMS to the mobile configured on the server. Charges may apply.</p>
<form method="post" action="/trigger-event">
<button type="submit">Trigger demo event and send SMS</button>
</form>
<p>Submit once. Do not refresh the result page or repeat an unclear request.</p>
</body>
</html>`, { headers: { ...headers, "Content-Type": "text/html; charset=utf-8" } });
    }
    if (url.pathname !== "/trigger-event") {
      return new Response("Not found", { status: 404, headers });
    }
    if (request.method !== "POST") {
      return new Response("Use POST", { status: 405, headers: { ...headers, Allow: "POST" } });
    }
    // Reject form submissions from other websites.
    if (request.headers.get("Origin") !== LOCAL_ORIGIN) {
      return new Response("Origin not permitted", { status: 403, headers });
    }
    if (!env.WORKER_API_KEY || !env.DEMO_MOBILE || !env.TNZ_SMS) {
      return new Response("Check the binding and local secrets", { status: 503, headers });
    }
    const event = { type: "demo.completed", reference: "DEMO-1001", mobile: env.DEMO_MOBILE };
    try {
      // A real application calls this after its chosen event has occurred.
      const response = await onApplicationEvent(event, env);
      return new Response(response.body, {
        status: response.status,
        headers: { ...headers, "Content-Type": "application/json" }
      });
    } catch {
      return Response.json({ error: "SMS outcome unknown; check before retrying" }, {
        status: 502, headers
      });
    }
  }
};

onApplicationEvent() builds the message from the event type and reference, then calls the SMS Worker.

The recipient comes from the server's DEMO_MOBILE setting. The form cannot choose another recipient or change the message.

3. Trigger the event and check the result

Live messages may incur charges. Use your own mobile number or a recipient who has agreed to the test. Check their opt-out status. Submit the demo once.

3.1 Choose live or test mode

To check the integration without sending a live SMS, change test: false to test: true in onApplicationEvent() before submitting the form.

3.2 Start the demo and trigger the event

  1. From application-event-demo, run npx wrangler dev --ip 127.0.0.1 --port 8787. Complete Cloudflare sign-in if prompted.
  2. Open http://127.0.0.1:8787/ in your browser. Use this exact address because the demo checks its origin.
  3. Select Trigger demo event and send SMS once.

3.3 Check the response and your phone

Check your phone for Demo: demo.completed (DEMO-1001). Save your actual messageId. The value above is illustrative.

Acceptance confirms submission, not handset delivery. See the setup guide to check delivery. Test requests do not send an SMS.

For an error, use the troubleshooting table. For an unclear result, follow the retry checks. Do not refresh the result page or submit the form again until you know the outcome.

4. Connect the handler to your application

4.1 Choose the event and message

Choose the event that should trigger an SMS, such as an order being dispatched or a report becoming available. These are examples of events your application could define.

Replace the demo message with wording for your chosen event.

4.2 Call the handler after the action succeeds

  1. Check that you selected this event type and have permission to message the recipient.
  2. Wait until the application action succeeds and its state is saved.
  3. Call onApplicationEvent() from your server-side logic. Pass the recipient and reference from trusted application records.

The /trigger-event route is a local demonstration, not a webhook receiver. For external events, verify the sender and validate the payload before creating your application's event.

4.3 Configure the application Worker

Add the service binding to your application Worker and store its matching WORKER_API_KEY as a deployed secret. Keep TNZ_AUTH_TOKEN in the SMS Worker.

4.4 Record results and add production controls

Record the SMS submission result separately from the application event. An SMS failure must not repeat or undo the business action that triggered it.

Before deployment, add the SMS Worker production controls, including event deduplication and checks for uncertain outcomes. The demo has no persistent state or duplicate protection.

4.5 Finish the local demonstration

  1. Stop Wrangler with Ctrl+C.
  2. Delete application-event-demo/.dev.vars when you no longer need its local credentials.

This leaves the deployed Worker’s secrets unchanged.

Keep the demo form local. It has no user login and is not a production application interface.

Troubleshooting

ProblemWhat to check
Application returns 403Open http://127.0.0.1:8787/ and submit its form. The demo rejects other origins.
Binding unavailableCheck TNZ_SMS, the deployed service name, remote: true and your Cloudflare account. Restart Wrangler after configuration changes.
503Check the local .dev.vars values and binding, then the SMS Worker's deployed secrets.

For other responses, use the SMS Worker troubleshooting guide. Follow its retry checks before repeating an uncertain request.


Topics:   Developers SMS

Start your free trial today!

Ready to revolutionise your messaging?
Connect with people anywhere - boost engagement, responses and conversations.

25 SMS Credits over 14-days. No obligation. No credit card required.