This is part of Thecore framework.
Web Push notifications (VAPID)
Self-contained browser push notifications — no Firebase, no APNs, no third-party account required. The gem provides the server-side half: models, dispatch service, and ActionCable channel. The client integration guide (React + service worker) lives in the model_driven_api README.
How it works
Browser Rails backend
│ │
│── POST subscribe ─────────►│ PushSubscriber.subscribe_for(user, endpoint:, p256dh:, auth:)
│ │
│◄─ ActionCable stream ──────│ PushNotificationChannel streams push_notifications_subscriber_N
│ │
│ [backend sends push] │ PushNotificationService.dispatch(subscriber, message)
│◄─ Web Push payload ────────│ → Webpush.payload_send (VAPID)
│ │ → message.sent_at = now
│── POST acknowledge ────────►│ message.update!(received_at: / read_at:)
Server configuration
VAPID keys are generated automatically the first time rails db:seed runs. You only need to set the contact email via RailsAdmin → Settings:
ThecoreSettings key (ns: :vapid) |
Purpose | Default |
|---|---|---|
public_key |
VAPID public key (base64url) — send to browsers | generated at seed |
private_key |
VAPID private key — never expose to clients | generated at seed |
contact_email |
mailto: URI in VAPID sub claim |
"" (set this) |
max_messages_per_subscriber |
PushMessage retention cap per subscriber | "500" |
Regenerating VAPID keys invalidates all existing
PushSubscriberrecords. Every registered browser must re-subscribe.
Models
PushSubscriber
Represents one browser/device subscription registered by a User.
| Column | Type | Notes |
|---|---|---|
user_id |
bigint | FK to users |
endpoint |
text | Unique; push service URL provided by the browser |
p256dh |
string | ECDH public key (base64url) |
auth |
string | Auth secret (base64url) |
user_agent |
string | Browser/OS identifier |
expired_at |
datetime | nil = active; set when the push service returns 410 |
# Upsert by endpoint (re-registering an existing browser updates the record)
PushSubscriber.subscribe_for(user, endpoint:, p256dh:, auth:, user_agent:)
# Scope: only active (not expired)
PushSubscriber.active
# Expire a subscriber (called automatically on 410 from push service)
subscriber.expire!
PushMessage
Records a notification payload and its lifecycle.
| Column | Type | Notes |
|---|---|---|
push_subscriber_id |
bigint | FK |
title |
string | Required |
body |
text | Required |
url |
string | URL to open on notification click (optional) |
icon |
string | Notification icon URL (optional) |
sent_at |
datetime | Populated by PushNotificationService on successful dispatch |
received_at |
datetime | Set by client via acknowledge endpoint |
read_at |
datetime | Set by client via acknowledge endpoint |
Old messages beyond vapid.max_messages_per_subscriber are pruned automatically after each dispatch (oldest first).
PushNotificationService
ThecoreBackendCommons::PushNotificationService.dispatch(subscriber, )
- Calls
Webpush.payload_sendwith the subscriber's keys and the VAPID credentials fromThecoreSettings. - On success: sets
message.sent_at = Time.current. - On
Webpush::ExpiredSubscriptionorWebpush::InvalidSubscription(HTTP 410/404 from push service): callssubscriber.expire!. - Prunes oldest
PushMessagerecords if the subscriber exceeds the retention cap. - Always returns
message— errors are rescued and logged, never propagated.
PushNotificationChannel (ActionCable)
# In your connection.rb — current_user must be set
class ApplicationCable::Connection < ActionCable::Connection::Base
identified_by :current_user
# ... authenticate and set current_user
end
Subscribe from the frontend:
// Stream for one subscriber (most common)
consumer.subscriptions.create(
{ channel: "PushNotificationChannel", subscriber_id: subscriberId },
{ received(data) { /* handle message */ } }
);
// Stream for all active subscribers of the current user
consumer.subscriptions.create(
{ channel: "PushNotificationChannel", user_id: currentUserId },
{ received(data) { /* handle message */ } }
);
The channel only streams to subscriber_id values that belong to current_user — unauthorized subscriber IDs are silently ignored.
Broadcast from anywhere in the backend:
PushNotificationChannel.broadcast_to(subscriber, )
# Sends message.as_json to "push_notifications_subscriber_#{subscriber.id}"
Invio email e configurazione SMTP
Configurazione
Le impostazioni SMTP sono salvate in ThecoreSettings (namespace :smtp) e vengono caricate automaticamente dai seeds del gem:
| Chiave | Descrizione |
|---|---|
address |
Indirizzo del server SMTP (obbligatorio per abilitare la consegna) |
port |
Porta (default 587); impostare 465 per SMTPS (SSL implicito) |
domain |
Dominio HELO |
user_name |
Username per l'autenticazione |
password |
Password per l'autenticazione |
authentication |
plain, login, cram_md5, oppure none/vuoto per disabilitare l'auth |
enable_starttls_auto |
Impostare a false, f, 0 o no per disabilitare STARTTLS. Ignorato se port è 465 (SMTPS usa TLS implicito). |
from |
Indirizzo mittente |
Nota porta 465 (SMTPS): la connessione viene wrappata in SSL dal primo byte. Impostare enable_starttls_auto a false non è sufficiente: occorre usare la porta 465 affinché SmtpConfig abiliti automaticamente tls: true. Le due modalità (SMTPS e STARTTLS) sono mutuamente esclusive — mescolarle causa un Net::ReadTimeout.
Usare SmtpDeliverable nei mailer
Includere il concern SmtpDeliverable in qualsiasi ActionMailer::Base per leggere le impostazioni SMTP da ThecoreSettings al momento dell'invio (nessun riavvio necessario quando le impostazioni cambiano):
class MyMailer < ApplicationMailer
include SmtpDeliverable
def my_email
mail(
from: smtp_setting(:from),
to: "recipient@example.com",
subject: "Hello"
)
end
end
Il concern registra automaticamente un after_action :configure_smtp_delivery che sovrascrive il delivery method con le impostazioni lette da ThecoreSettings. Se smtp.address è vuoto, il mailer usa il delivery method configurato nell'ambiente Rails (es. :test o :letter_opener in sviluppo).
Il metodo helper smtp_setting(key) è disponibile in tutta la classe mailer.
Usare SmtpConfig direttamente
ThecoreBackendCommons::SmtpConfig è un modulo Ruby puro utilizzabile anche al di fuori dei mailer (es. script, job, rake task):
opts = ThecoreBackendCommons::SmtpConfig.
# => { address: "smtp.example.com", port: 465, tls: true, ... }
ThecoreBackendCommons::SmtpConfig.setting(:from)
# => "noreply@example.com"
Testare la configurazione SMTP
Il gem espone ThecoreBackendCommons::SmtpTester e un rake task, disponibili automaticamente in qualsiasi app che include questo gem.
Da rake task (shell):
rails thecore_backend_commons:smtp:test[destinatario@example.com]
Da Rails console:
ThecoreBackendCommons::SmtpTester.call("destinatario@example.com")
Se non si passa un indirizzo, in entrambi i casi viene usato ThecoreSettings mytask.default_email.
Lo tester stampa i parametri SMTP effettivamente usati (inclusi tls e enable_starttls_auto) prima di tentare la consegna. Restituisce true/false dal console e exit code 1 dal rake task in caso di errore.