Implementing Push Notifications with ntfy.sh in Property Web Builder: A Complete Technical Guide
Property Web Builder implements push notifications with ntfy.sh through a tenant-aware, three-layer architecture that combines configuration persistence in ActiveRecord, a dedicated service class for HTTP orchestration, and background jobs for asynchronous delivery.
Property Web Builder is an open-source Rails platform designed for multi-tenant real estate websites. The application natively integrates push notifications with ntfy.sh to alert administrators about inquiries, listing changes, and security events without requiring external SaaS providers or complex mobile SDKs.
Architecture Overview
The notification system follows a strict separation of concerns across three layers. The Pwb::Website model stores tenant-specific configuration and feature flags. The NtfyService class handles topic construction, HTTP header assembly, and error logging. Finally, NtfyNotificationJob decouples network latency from the web request cycle using ActiveJob.
This design ensures that each tenant operates in complete isolation. Topic prefixes prevent notification leakage between websites sharing the same ntfy.sh server instance.
Configuring the Pwb::Website Model
In app/models/pwb/website.rb (lines 49-56), the website model persists all ntfy configuration as standard ActiveRecord attributes. The schema migration at db/migrate/20251208134208_add_ntfy_settings_to_websites.rb (lines 4-13) establishes the required boolean and string columns.
Key configuration attributes include:
ntfy_server_url: The ntfy endpoint (defaults tohttps://ntfy.shfor public instances or custom URLs for self-hosted deployments)ntfy_topic_prefix: A string prepended to all topics for tenant isolationntfy_access_token: Optional bearer token for publishing to private ntfy topicsntfy_notify_inquiries,ntfy_notify_listings: Boolean flags gating specific notification channels
The model provides the enabled_for?(channel) method, which NtfyService consults before dispatching any payload.
The NtfyService Implementation
Located in app/services/ntfy_service.rb (lines 17-62), this service class encapsulates the ntfy protocol implementation. It transforms internal domain events into properly formatted HTTP POST requests with the correct header schema required by ntfy.sh.
HTTP Publishing Mechanics
The private publish method constructs the topic string using the pattern <prefix>-<channel>. It assembles mandatory HTTP headers including Title, Priority, Tags, Click, and Actions, then executes the POST request to the configured server URL. Errors and timeouts are captured and logged via StructuredLogger to prevent job failures from losing notification state.
Notification Type Methods
The service exposes specific methods for each business event:
test_configuration(website)(lines 64-84): Validates connectivity by sending a test payload to the configured servernotify_inquiry(website, message)(lines 35-51): Formats contact form submissions with inquiry metadatanotify_listing_change(website, listing, change_type)(lines 53-71): Handles property publication status changes, utilizing thelisting_change_contenthelper (lines 309-352) for body formattingnotify_security_event(website, event_type, metadata)(lines 73-76): Dispatches authentication and access anomaly alerts
Each method respects the tenant's feature flags before invoking publish.
Asynchronous Delivery with NtfyNotificationJob
The app/jobs/ntfy_notification_job.rb file (lines 19-52) defines a TenantAwareJob that switches database context to the correct website before executing. This prevents cross-tenant data leakage in multi-threaded job processors.
The job accepts parameters for the website ID, notification type, record ID, and optional metadata. It retrieves the relevant ActiveRecord object (such as Pwb::Message or Pwb::SaleListing) and delegates to the appropriate NtfyService method. By queuing the HTTP POST operation asynchronously, the application maintains sub-100ms response times on web endpoints regardless of ntfy.sh latency.
Typical execution flow:
- User submits contact form → Rails controller creates
Pwb::Messagerecord - Model callback triggers
NtfyNotificationJob.perform_later(website.id, :inquiry, message.id) - Job processor switches tenant context and loads the message
- Job calls
NtfyService.notify_inquiry(website, message) - Service POSTs to
https://ntfy.sh/<topic>with structured headers
Implementation Examples
Testing Connectivity from the Rails Console
Verify integration settings before enabling notifications in production:
website = Pwb::Website.find(1)
result = NtfyService.test_configuration(website)
puts result[:message] # => "Test notification sent successfully"
Triggering Inquiry Notifications Manually
While typically invoked via callbacks, you can manually dispatch inquiry alerts:
message = Pwb::Message.find(42)
NtfyService.notify_inquiry(message.website, message)
Automating Listing Change Alerts
Add real-time publication notifications using ActiveRecord callbacks:
class Pwb::SaleListing < ApplicationRecord
after_update :notify_if_published
private
def notify_if_published
return unless saved_change_to_attribute?(:published) && published?
NtfyService.notify_listing_change(website, self, :published)
end
end
Enqueueing Security Events
For non-persisted events like failed logins, pass raw metadata to the job:
NtfyNotificationJob.perform_later(
website.id,
:security,
nil,
nil,
'login_failed',
{ email: user.email, ip: request.remote_ip }
)
The job routes this to NtfyService.notify_security_event for immediate dispatch.
Summary
- Tenant isolation is enforced through topic prefixes stored in
ntfy_topic_prefixon thePwb::Websitemodel - Feature gating relies on boolean columns (
ntfy_notify_inquiries,ntfy_notify_listings) checked viaenabled_for?inapp/models/pwb/website.rb - Protocol compliance is handled by
NtfyServiceinapp/services/ntfy_service.rb, which constructs proper ntfy HTTP headers and manages error logging - Async delivery is guaranteed by
NtfyNotificationJobinapp/jobs/ntfy_notification_job.rb, preventing request-cycle blocking - Extensibility requires only adding methods to
NtfyService, corresponding migration columns, and job routing logic
Frequently Asked Questions
How do I enable push notifications for a specific property website?
Set the ntfy_server_url (defaulting to https://ntfy.sh), define a unique ntfy_topic_prefix for isolation, and toggle the relevant boolean columns such as ntfy_notify_inquiries to true on the Pwb::Website record. The NtfyService automatically validates these flags via enabled_for? before transmitting any payload.
Can Property Web Builder use a private ntfy instance instead of ntfy.sh?
Yes. Configure the ntfy_server_url attribute on the website model to point to your self-hosted ntfy server. If the server requires authentication, populate the ntfy_access_token column; the service includes this as a Bearer token in the Authorization header of all POST requests.
What happens when the ntfy server is unreachable?
The publish method in app/services/ntfy_service.rb wraps HTTP operations in exception handling that logs failures through StructuredLogger. Because NtfyNotificationJob processes delivery asynchronously, ActiveJob's built-in retry mechanisms automatically reattempt failed transmissions according to your queue adapter configuration.
How do I add a custom notification channel for new business events?
Extend app/services/ntfy_service.rb with a new method following the pattern of notify_inquiry, define a channel constant for the topic suffix, add a corresponding boolean enablement column to pwb_websites via a Rails migration, and update NtfyNotificationJob to route the new event type. The existing infrastructure handles topic construction, tenant context switching, and HTTP delivery automatically.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →