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.
-
Application Worker Trigger demo event
-
SMS Worker Check and Submit
-
TNZ REST API Send SMS
-
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:
cd <your-project-folder>
npm create cloudflare@latest -- application-event-demo
At the prompts, select:
- Hello World example
- Worker only
- JavaScript
- No when asked whether to deploy
1.2 Open the project folder
Run:
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.
"services": [
{
"binding": "TNZ_SMS",
"service": "tnz-worker",
"remote": true
}
]
bindinggives the application access toenv.TNZ_SMS.servicemust match the deployed SMS Worker'snamein its Wrangler configuration. Changetnz-workerhere if you chose another name.remote: trueconnects 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:
WORKER_API_KEY="YOUR_SMS_WORKER_KEY"
DEMO_MOBILE="YOUR_MOBILE_NUMBER"
- Replace
YOUR_SMS_WORKER_KEYwith the matchingWORKER_API_KEYfrom your SMS Worker. - Replace
YOUR_MOBILE_NUMBERwith your own NZ or AU mobile number. See supported number formats. - Check that
.dev.vars*is inapplication-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
onApplicationEvent() sends the event message. The remaining handler serves the local demo form.
Replace application-event-demo/src/index.js with this complete demonstration:
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
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
- From
application-event-demo, runnpx wrangler dev --ip 127.0.0.1 --port 8787. Complete Cloudflare sign-in if prompted. - Open
http://127.0.0.1:8787/in your browser. Use this exact address because the demo checks its origin. - 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
- Check that you selected this event type and have permission to message the recipient.
- Wait until the application action succeeds and its state is saved.
- 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
- Stop Wrangler with Ctrl+C.
- Delete
application-event-demo/.dev.varswhen 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
| Problem | What to check |
|---|---|
| Application returns 403 | Open http://127.0.0.1:8787/ and submit its form. The demo rejects other origins. |
| Binding unavailable | Check TNZ_SMS, the deployed service name, remote: true and your Cloudflare account. Restart Wrangler after configuration changes. |
| 503 | Check 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