How to Send a Ruby Push Notification to Your iPhone
Send a Ruby push notification to your iPhone in about five lines: no gems, no APNs setup. Plain Net::HTTP, plus a Rails example.

Your Sidekiq job swallowed an exception at 6 a.m. and quietly retried itself into oblivion. You found out hours later, when a customer asked why their export came back empty. A push to your phone would have told you in seconds, and from Ruby, sending one is about five lines.
Ruby gives you plenty of ways to catch a failure: a Sidekiq retry, a rescue block, an exception tracker filling up with rows. Every one of them assumes you're at a screen, looking. A push notification doesn't wait for you to check: it lands on your phone the second the rescue block runs, whether you're at your desk or on the train.
Most "send a notification from Ruby" tutorials reach for a gem, an APNs certificate, a provider account, and an OAuth dance. For a personal ping, the job died, the deploy shipped, someone signed up: that's wildly out of proportion. TheNotificationApp is a single HTTP endpoint: one POST, one header, a JSON body. No gem in your Gemfile, no certificate to rotate. Here's the whole thing.
What you need
- A TheNotificationApp account and an
app_key: the free tier is enough to follow along. - The iOS app installed and signed in with the same account, so the notification has somewhere to land.
- Ruby. That's it.
Net::HTTPships in the standard library, so there are no gems to install.
The five lines
Drop your key in an environment variable and fire a POST at the endpoint. This is the entire integration:
require "net/http"
require "json"
Net::HTTP.post(
URI("https://thenotification.app/api/sendNotification"),
{ title: "Deploy finished", body: "Build #4821 is live in production." }.to_json,
"app_key" => ENV["NOTIFICATION_APP_KEY"], "Content-Type" => "application/json"
)Run it with NOTIFICATION_APP_KEY=your_key ruby notify.rb and your phone buzzes. The .to_json is the part that's easy to forget. The API expects a JSON body, and a Ruby hash isn't one until you convert it. Skip it and you'll get a 400 back.
A method you'll actually reuse
An inline call is fine for a throwaway script. For anything you'll call from more than one place, wrap it once so the URL, headers, and error handling live in a single spot:
require "net/http"
require "json"
require "uri"
def notify(title, body, **extra)
uri = URI("https://thenotification.app/api/sendNotification")
payload = { title: title, body: body }.merge(extra).to_json
res = Net::HTTP.post(
uri,
payload,
"app_key" => ENV.fetch("NOTIFICATION_APP_KEY"),
"Content-Type" => "application/json"
)
raise "Notification failed: #{res.code} #{res.body}" unless res.code == "200"
JSON.parse(res.body)
end
notify("Backup done", "Nightly dump finished in 4m 12s.")Two small choices earn their keep here. ENV.fetch raises if the key is missing, so a misconfigured server fails loudly instead of sending nothing. And raising on any non-200 means a broken notification can't hide a broken job: the failure surfaces where you'd expect it.
Sending more than text
Two optional fields are worth knowing about:
link: a URL the notification opens when you tap it. Point it straight at the PR, the dashboard, or the failed run.image: an image URL shown inside the notification.
Because notify takes keyword args, you pass them through without touching the method:
notify(
"New signup",
"alex@example.com just created an account.",
link: "https://dash.example.com/users/8842"
)Both are optional, leave them off and you get a plain title-and-body push. There are only four fields total, so there's nothing to memorize; the API reference has the complete list.
Wiring it into a Rails app
Calling a push notification API from Rails works best where you already care that something went sideways: a background job or a rescue block. A job is the cleanest home, because failures there are otherwise invisible until someone complains:
class ImportJob < ApplicationJob
rescue_from(StandardError) do |error|
notify("Import job failed", "#{self.class.name}: #{error.message}")
raise error
end
def perform(csv_path)
Importer.run(csv_path)
end
endThe rescue_from fires the push and then re-raises, so Active Job (or Sidekiq) still records the failure and retries as configured, you get the alert and keep the normal behavior. Put notify in a small lib/ file or a service object so jobs, mailers, and rake tasks can all reach it. One thing to avoid: don't block a web request on the call. An HTTP round-trip to an external service in the request cycle adds latency to a page load, push it to a job instead.
Reading the response
A success comes back as JSON you can log or ignore:
{ "success": true, "message": "Notification sent to 1 device(s)", "devices_sent": 1, "failed_count": 0 }The codes worth handling: 401 means a missing or wrong app_key, 400 means you left out title or body, and 429 means you hit your limit: the body tells you the cap:
{ "success": false, "error": "Monthly message limit exceeded", "limit": 100, "received": 101 }The notify method above turns each of those into a raised exception, which inside a job means they land in the same place as every other failure you already track. Worth a glance at the rate limits page if you're wiring this into something busy.
The honest tradeoff
Fair warning on the numbers: the free tier is 100 notifications total, not per month, total, across up to 3 apps. That's plenty to wire up "deploy shipped" and "nightly job died" and then forget the whole thing exists. But point it at a busy Sidekiq queue that retries on every blip and you'll burn through 100 in an afternoon. Pro is $2.99/month for 1,000 notifications a month and up to 10 apps. Either way, put a threshold in front of anything chatty, notify on the failure, not on each of 500 retries.
That's the whole thing
No gem, no certificate, one POST you can read at a glance. If you've got a Ruby script or a Rails job whose failures you currently hear about from users first, this is worth a look. You can start free at thenotification.app. Working in more than one language? The Python and Node.js versions are the same idea with different syntax.
New to this? Start with what a push notification API actually is.



