nuntius 1.4.15 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +8 -1
- data/app/controllers/nuntius/admin/campaigns_controller.rb +1 -1
- data/app/controllers/nuntius/admin/lists/subscribers_controller.rb +24 -0
- data/app/controllers/nuntius/subscribers_controller.rb +37 -0
- data/app/drops/nuntius/campaign_drop.rb +1 -1
- data/app/drops/nuntius/message_drop.rb +1 -1
- data/app/drops/nuntius/subscriber_drop.rb +9 -1
- data/app/helpers/nuntius/application_helper.rb +1 -0
- data/app/jobs/nuntius/campaign_publish_job.rb +9 -0
- data/app/jobs/nuntius/import_subscribers_job.rb +36 -0
- data/app/jobs/nuntius/purge_message_job.rb +1 -1
- data/app/jobs/nuntius/transport_delivery_job.rb +1 -1
- data/app/messengers/nuntius/message_messenger.rb +21 -0
- data/app/models/nuntius/message.rb +36 -24
- data/app/providers/nuntius/apnotic_push_provider.rb +3 -3
- data/app/providers/nuntius/firebase_push_provider.rb +3 -3
- data/app/providers/nuntius/message_bird_sms_provider.rb +3 -3
- data/app/providers/nuntius/slack_slack_provider.rb +3 -3
- data/app/providers/nuntius/smstools_sms_provider.rb +1 -1
- data/app/providers/nuntius/smtp_mail_provider.rb +7 -7
- data/app/providers/nuntius/teams_teams_provider.rb +3 -3
- data/app/providers/nuntius/twilio_sms_provider.rb +2 -2
- data/app/providers/nuntius/twilio_voice_provider.rb +2 -2
- data/app/services/nuntius/aws_sns_processor_service.rb +3 -3
- data/app/tables/nuntius/messages_table.rb +1 -1
- data/app/views/nuntius/admin/campaigns/edit.html.slim +6 -4
- data/app/views/nuntius/admin/lists/subscribers/import.html.slim +15 -0
- data/app/views/nuntius/admin/messages/show.html.slim +1 -1
- data/app/views/nuntius/subscribers/edit.html.slim +20 -0
- data/config/locales/en.yml +15 -0
- data/config/locales/nl.yml +17 -1
- data/config/routes.rb +6 -1
- data/db/migrate/20260324140324_add_publish_at_to_campaign.rb +5 -0
- data/db/migrate/20260324150714_rename_status_to_state.rb +5 -0
- data/lib/nuntius/version.rb +1 -1
- metadata +9 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 25dbff01510e68a23dbd0bf046aefd97cae3ad2633c88bb8cd702d42639b6a50
|
|
4
|
+
data.tar.gz: 4c87bfd9ba23075725696538ee1fb1e1d9ba0661e07704b23ac3f75fbfb2f00c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 47904897c1e9c09e5a1dab2bb82a979bc312f401c7d69766947e275729a30be0cb41f923614b2c558dd24edec2daacbe5b1c4fd2a0b22173aa7a5e68feb0a9c2
|
|
7
|
+
data.tar.gz: a307808cc8cac2b8b8077ea9c78f3da025b830367a4b5c8ebc8eaacacd0e985f4fe0e90c866e348246c02725ef0b6f5c0a9a3440a854bd07a9291e6fbe0082a3
|
data/README.md
CHANGED
|
@@ -357,10 +357,17 @@ class BarMessageBox < Nuntius::BaseMessageBox
|
|
|
357
357
|
route to: :process
|
|
358
358
|
|
|
359
359
|
def process
|
|
360
|
-
puts message.
|
|
360
|
+
puts message.state # message is Nuntius's inbound message.
|
|
361
361
|
puts mail.to # mail gives you the Mail representation, when it's a mail (transport)
|
|
362
362
|
end
|
|
363
363
|
end
|
|
364
364
|
```
|
|
365
365
|
|
|
366
366
|
Add `Nuntius::RetrieveMailJob` to your cron.
|
|
367
|
+
|
|
368
|
+
## Campaigns
|
|
369
|
+
|
|
370
|
+
Campaigns allows you to bulk send many mails to subscribers.
|
|
371
|
+
We support multiple lists with many subscribers.
|
|
372
|
+
|
|
373
|
+
You can manually publish the campaign or have Nuntius publish the campaign after a certain date/time. If you want to have Nuntius publish it for you, add `Nuntius::CampaignPublishJob` to your cron.
|
|
@@ -51,7 +51,7 @@ module Nuntius
|
|
|
51
51
|
def campaign_params
|
|
52
52
|
return unless params[:campaign]
|
|
53
53
|
|
|
54
|
-
params.require(:campaign).permit(:name, :transport, :layout_id, :list_id, :from, :subject, :text, :html, :metadata_yaml, :open_tracking, :link_tracking)
|
|
54
|
+
params.require(:campaign).permit(:name, :transport, :layout_id, :list_id, :from, :subject, :text, :html, :metadata_yaml, :open_tracking, :link_tracking, :publish_at)
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
57
|
end
|
|
@@ -12,6 +12,26 @@ module Nuntius
|
|
|
12
12
|
@subscribers = @list.subscribers.all
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
def import
|
|
16
|
+
return unless request.post?
|
|
17
|
+
|
|
18
|
+
file = params[:file]
|
|
19
|
+
if file.blank?
|
|
20
|
+
Signum.error(Current.user, text: t(".no_file"))
|
|
21
|
+
redirect_to import_admin_list_subscribers_path(@list) and return
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
blob = ActiveStorage::Blob.create_and_upload!(
|
|
25
|
+
io: file,
|
|
26
|
+
filename: file.original_filename,
|
|
27
|
+
content_type: "text/csv"
|
|
28
|
+
)
|
|
29
|
+
ImportSubscribersJob.perform_later(@list, blob, Current.user)
|
|
30
|
+
|
|
31
|
+
Signum.info(Current.user, text: t(".queued"))
|
|
32
|
+
redirect_to nuntius.admin_list_path(@list)
|
|
33
|
+
end
|
|
34
|
+
|
|
15
35
|
def new
|
|
16
36
|
@subscriber = @list.subscribers.new
|
|
17
37
|
render :edit
|
|
@@ -41,6 +61,10 @@ module Nuntius
|
|
|
41
61
|
params.require(:subscriber).permit(:first_name, :last_name, :email, :phone_number, :metadata_yaml, tags: [])
|
|
42
62
|
end
|
|
43
63
|
|
|
64
|
+
def import_params
|
|
65
|
+
params.permit(:file)
|
|
66
|
+
end
|
|
67
|
+
|
|
44
68
|
def set_objects
|
|
45
69
|
@list = List.find(params[:list_id])
|
|
46
70
|
end
|
|
@@ -8,6 +8,37 @@ module Nuntius
|
|
|
8
8
|
skip_before_action :verify_authenticity_token, only: :unsubscribe
|
|
9
9
|
layout "empty"
|
|
10
10
|
|
|
11
|
+
def new
|
|
12
|
+
@subscriber = Nuntius::Subscriber.new(list: Nuntius::List.find(params[:list_id]))
|
|
13
|
+
render :edit
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create
|
|
17
|
+
@subscriber = Nuntius::Subscriber.new(subscriber_params)
|
|
18
|
+
if @subscriber.save
|
|
19
|
+
Signum.success(request.session.id, text: "You have been subscribed.")
|
|
20
|
+
redirect_to nuntius.edit_subscriber_path(@subscriber), status: :see_other
|
|
21
|
+
else
|
|
22
|
+
render :edit, status: :unprocessable_entity
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def edit
|
|
27
|
+
@subscriber = Nuntius::Subscriber.find(params[:id])
|
|
28
|
+
@list = @subscriber.list
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def update
|
|
32
|
+
@subscriber = Nuntius::Subscriber.find(params[:id])
|
|
33
|
+
@list = @subscriber.list
|
|
34
|
+
if @subscriber.update(subscriber_params)
|
|
35
|
+
Signum.success(request.session.id, text: "Subscription has been updated.")
|
|
36
|
+
redirect_to nuntius.edit_subscriber_path(@subscriber), status: :see_other
|
|
37
|
+
else
|
|
38
|
+
render :edit, status: :unprocessable_entity
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
11
42
|
def show
|
|
12
43
|
@subscriber = Nuntius::Subscriber.find_by(id: params[:id])
|
|
13
44
|
if @subscriber
|
|
@@ -32,5 +63,11 @@ module Nuntius
|
|
|
32
63
|
Signum.success(request.session.id, text: "Subscription has been removed.")
|
|
33
64
|
redirect_to nuntius.subscriber_path(@subscriber), status: :see_other
|
|
34
65
|
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def subscriber_params
|
|
70
|
+
params.require(:subscriber).permit(:email, :list_id, :first_name, :last_name, :phone_number)
|
|
71
|
+
end
|
|
35
72
|
end
|
|
36
73
|
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Nuntius
|
|
4
4
|
class MessageDrop < ApplicationDrop
|
|
5
|
-
delegate :id, :from, :to, :subject, :html, :text, to: :@object
|
|
5
|
+
delegate :id, :from, :to, :subject, :html, :text, :template, :campaign, to: :@object
|
|
6
6
|
|
|
7
7
|
def base_url
|
|
8
8
|
Nuntius::Engine.routes.url_helpers.message_url(@object.id, host: host)
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module Nuntius
|
|
4
4
|
class SubscriberDrop < ApplicationDrop
|
|
5
|
-
delegate :id, :first_name, :last_name, :name, :list, :metadata, to: :@object
|
|
5
|
+
delegate :id, :first_name, :last_name, :name, :list, :metadata, :unsubscribed_at, to: :@object
|
|
6
|
+
|
|
7
|
+
def subscribed?
|
|
8
|
+
@object.unsubscribed_at.nil?
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def unsubscribed?
|
|
12
|
+
!subscribed?
|
|
13
|
+
end
|
|
6
14
|
end
|
|
7
15
|
end
|
|
@@ -47,6 +47,7 @@ module Nuntius
|
|
|
47
47
|
def nuntius_list_menu
|
|
48
48
|
Satis::Menus::Builder.build([:nuntius, :lists]) do |m|
|
|
49
49
|
m.item :new_subscriber, link: nuntius.new_admin_list_subscriber_path(@list) if @list.persisted?
|
|
50
|
+
m.item :import_subscribers, link: nuntius.import_admin_list_subscribers_path(@list) if @list.persisted?
|
|
50
51
|
end
|
|
51
52
|
end
|
|
52
53
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "csv"
|
|
4
|
+
|
|
5
|
+
module Nuntius
|
|
6
|
+
class ImportSubscribersJob < ApplicationJob
|
|
7
|
+
KNOWN_COLUMNS = %i[first_name last_name email phone_number].freeze
|
|
8
|
+
|
|
9
|
+
def perform(list, blob, user)
|
|
10
|
+
csv_content = blob.download
|
|
11
|
+
|
|
12
|
+
imported = 0
|
|
13
|
+
failed = 0
|
|
14
|
+
|
|
15
|
+
CSV.parse(csv_content, headers: true, header_converters: :symbol) do |row|
|
|
16
|
+
row_hash = row.to_h
|
|
17
|
+
attrs = row_hash.slice(*KNOWN_COLUMNS)
|
|
18
|
+
extra = row_hash.except(*KNOWN_COLUMNS).reject { |_, v| v.nil? }
|
|
19
|
+
attrs[:metadata] = extra unless extra.empty?
|
|
20
|
+
|
|
21
|
+
subscriber = list.subscribers.new(attrs)
|
|
22
|
+
if subscriber.save
|
|
23
|
+
imported += 1
|
|
24
|
+
else
|
|
25
|
+
failed += 1
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
Signum.success(user, text: I18n.t("nuntius.admin.lists.subscribers.import.success", imported: imported, failed: failed))
|
|
30
|
+
rescue CSV::MalformedCSVError => e
|
|
31
|
+
Signum.error(user, text: I18n.t("nuntius.admin.lists.subscribers.import.invalid_csv", message: e.message))
|
|
32
|
+
ensure
|
|
33
|
+
blob.purge
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -3,7 +3,7 @@ module Nuntius
|
|
|
3
3
|
def perform(account_id, months)
|
|
4
4
|
messages = Nuntius::Message.distinct.select(:id).where("metadata ->> 'account_id' = :account", account: account_id)
|
|
5
5
|
.where(created_at: ..months.months.ago.beginning_of_day)
|
|
6
|
-
.where.not(
|
|
6
|
+
.where.not(state: %w[complaint bounced])
|
|
7
7
|
|
|
8
8
|
Nuntius::Message.where(parent_message_id: messages.pluck(:id)).in_batches.update_all(parent_message_id: nil)
|
|
9
9
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nuntius
|
|
4
|
+
class MessageMessenger < ApplicationMessenger
|
|
5
|
+
template_scope ->(screening) { all }
|
|
6
|
+
|
|
7
|
+
# For an after scope the time_range the interval is taken from the current time, the end of the
|
|
8
|
+
# range is 1 hour from its start.
|
|
9
|
+
timebased_scope :after_opened do |time_range, metadata|
|
|
10
|
+
Screening.where("opened_at BETWEEN ? AND ?", time_range.first, time_range.last)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
timebased_scope :after_clicked do |time_range, metadata|
|
|
14
|
+
Screening.where("clicked_at BETWEEN ? AND ?", time_range.first, time_range.last)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
timebased_scope :after_sent do |time_range, metadata|
|
|
18
|
+
Screening.where("last_sent_at BETWEEN ? AND ?", time_range.first, time_range.last)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -29,36 +29,48 @@ module Nuntius
|
|
|
29
29
|
|
|
30
30
|
before_destroy :cleanup_attachments
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
32
|
+
state_machine initial: :pending do
|
|
33
|
+
event :pending do
|
|
34
|
+
transition any => :pending
|
|
35
|
+
end
|
|
36
|
+
event :sent do
|
|
37
|
+
transition any => :sent
|
|
38
|
+
end
|
|
39
|
+
event :blocked do
|
|
40
|
+
transition any => :blocked
|
|
41
|
+
end
|
|
42
|
+
event :delivered do
|
|
43
|
+
transition any => :delivered
|
|
44
|
+
end
|
|
45
|
+
event :undelivered do
|
|
46
|
+
transition any => :undelivered
|
|
47
|
+
end
|
|
48
|
+
event :rejected do
|
|
49
|
+
transition any => :rejected
|
|
50
|
+
end
|
|
51
|
+
event :bounced do
|
|
52
|
+
transition any => :bounced
|
|
53
|
+
end
|
|
54
|
+
event :complaint do
|
|
55
|
+
transition any => :complaint
|
|
56
|
+
end
|
|
57
|
+
event :no_recipient do
|
|
58
|
+
transition any => :no_recipient
|
|
59
|
+
end
|
|
60
|
+
event :opened do
|
|
61
|
+
transition any => any
|
|
62
|
+
end
|
|
63
|
+
event :clicked do
|
|
64
|
+
transition any => any
|
|
65
|
+
end
|
|
48
66
|
end
|
|
49
67
|
|
|
50
|
-
|
|
51
|
-
status == "delivered"
|
|
52
|
-
end
|
|
68
|
+
nuntiable use_state_machine: true
|
|
53
69
|
|
|
54
70
|
def delivered_or_blocked?
|
|
55
71
|
delivered? || blocked?
|
|
56
72
|
end
|
|
57
73
|
|
|
58
|
-
def undelivered?
|
|
59
|
-
status == "undelivered"
|
|
60
|
-
end
|
|
61
|
-
|
|
62
74
|
def opened?
|
|
63
75
|
opened_at.present?
|
|
64
76
|
end
|
|
@@ -69,7 +81,7 @@ module Nuntius
|
|
|
69
81
|
|
|
70
82
|
# Removes only pending child messages
|
|
71
83
|
def cleanup!
|
|
72
|
-
Nuntius::Message.where(
|
|
84
|
+
Nuntius::Message.where(state: "pending").where(parent_message: self).destroy_all
|
|
73
85
|
end
|
|
74
86
|
|
|
75
87
|
def add_attachment(options)
|
|
@@ -14,10 +14,10 @@ module Nuntius
|
|
|
14
14
|
options = (message.payload || {}).merge(data: {body: message.text})
|
|
15
15
|
response = fcm.send([message.to], options)
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
if response[:status_code] != 200 || response[:response] != "success"
|
|
18
|
+
message.undelivered
|
|
19
19
|
else
|
|
20
|
-
|
|
20
|
+
message.sent
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
message
|
|
@@ -16,15 +16,15 @@ module Nuntius
|
|
|
16
16
|
def deliver
|
|
17
17
|
response = client.message_create(message.from.present? ? message.from : from, message.to, message.text)
|
|
18
18
|
message.provider_id = response.id
|
|
19
|
-
message.
|
|
19
|
+
message.send(translated_status(response.recipients["items"].first.status))
|
|
20
20
|
message
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def refresh
|
|
24
24
|
response = client.message(message.provider_id)
|
|
25
25
|
message.provider_id = response.id
|
|
26
|
-
message.
|
|
27
|
-
Nuntius.config.logger.call.info "SMS #{message.to}
|
|
26
|
+
message.send(translated_status(response.recipients["items"].first.status))
|
|
27
|
+
Nuntius.config.logger.call.info "SMS #{message.to} state: #{message.state}"
|
|
28
28
|
message
|
|
29
29
|
rescue => _e
|
|
30
30
|
message
|
|
@@ -24,10 +24,10 @@ module Nuntius
|
|
|
24
24
|
args = (message.payload || {}).merge(channel: message[:to], text: message.text, as_user: true, username: message[:from])
|
|
25
25
|
response = client.chat_postMessage(args.deep_symbolize_keys)
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
if response["ok"]
|
|
28
|
+
message.sent
|
|
29
29
|
else
|
|
30
|
-
|
|
30
|
+
message.undelivered
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
message
|
|
@@ -17,7 +17,7 @@ module Nuntius
|
|
|
17
17
|
def deliver
|
|
18
18
|
response = client.messages.create(sender: message.from.present? ? message.from : from, to: message.to, message: message.text)
|
|
19
19
|
message.provider_id = response.body.messageid
|
|
20
|
-
message.
|
|
20
|
+
message.send(translated_status(response.status))
|
|
21
21
|
message
|
|
22
22
|
end
|
|
23
23
|
|
|
@@ -17,7 +17,7 @@ module Nuntius
|
|
|
17
17
|
def deliver
|
|
18
18
|
return no_recipient if message.to.blank?
|
|
19
19
|
return block unless MailAllowList.new(settings[:allow_list]).allowed?(message.to)
|
|
20
|
-
return block if Nuntius::Message.where(
|
|
20
|
+
return block if Nuntius::Message.where(state: %w[complaint bounced], to: message.to).count >= 1
|
|
21
21
|
|
|
22
22
|
mail = if message.from.present?
|
|
23
23
|
Mail.new(sender: from_header, from: message.from)
|
|
@@ -73,28 +73,28 @@ module Nuntius
|
|
|
73
73
|
begin
|
|
74
74
|
response = mail.deliver!
|
|
75
75
|
rescue Net::SMTPFatalError
|
|
76
|
-
message.
|
|
76
|
+
message.rejected
|
|
77
77
|
return message
|
|
78
78
|
rescue Net::SMTPServerBusy, Net::ReadTimeout
|
|
79
|
-
message.
|
|
79
|
+
message.undelivered
|
|
80
80
|
return message
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
message.provider_id = mail.message_id
|
|
84
|
-
message.
|
|
85
|
-
message.
|
|
84
|
+
message.undelivered
|
|
85
|
+
message.sent if Rails.env.test? || response.success?
|
|
86
86
|
message.last_sent_at = Time.zone.now if message.sent?
|
|
87
87
|
|
|
88
88
|
message
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def block
|
|
92
|
-
message.
|
|
92
|
+
message.blocked
|
|
93
93
|
message
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
def no_recipient
|
|
97
|
-
message.
|
|
97
|
+
message.no_recipient
|
|
98
98
|
message
|
|
99
99
|
end
|
|
100
100
|
|
|
@@ -13,10 +13,10 @@ module Nuntius
|
|
|
13
13
|
args = (message.payload || {}).merge(text: message.text)
|
|
14
14
|
response = Faraday.post(message[:to], JSON.dump(args), {"Content-Type": "application/json"})
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
if response.status == 200
|
|
17
|
+
message.sent
|
|
18
18
|
else
|
|
19
|
-
|
|
19
|
+
message.undelivered
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
message
|
|
@@ -17,14 +17,14 @@ module Nuntius
|
|
|
17
17
|
def deliver
|
|
18
18
|
response = client.messages.create(from: message.from.present? ? message.from : from, to: message.to, body: message.text)
|
|
19
19
|
message.provider_id = response.sid
|
|
20
|
-
message.
|
|
20
|
+
message.send(translated_status(response.status))
|
|
21
21
|
message
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def refresh
|
|
25
25
|
response = client.messages(message.provider_id).fetch
|
|
26
26
|
message.provider_id = response.sid
|
|
27
|
-
message.
|
|
27
|
+
message.send(translated_status(response.status))
|
|
28
28
|
message
|
|
29
29
|
end
|
|
30
30
|
|
|
@@ -19,14 +19,14 @@ module Nuntius
|
|
|
19
19
|
# Need hostname here too
|
|
20
20
|
response = client.calls.create(from: message.from.present? ? message.from : from, to: message.to, method: "POST", url: callback_url)
|
|
21
21
|
message.provider_id = response.sid
|
|
22
|
-
message.
|
|
22
|
+
message.send(translated_status(response.status))
|
|
23
23
|
message
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def refresh
|
|
27
27
|
response = client.calls(message.provider_id).fetch
|
|
28
28
|
message.provider_id = response.sid
|
|
29
|
-
message.
|
|
29
|
+
message.send(translated_status(response.status))
|
|
30
30
|
message
|
|
31
31
|
end
|
|
32
32
|
|
|
@@ -53,19 +53,19 @@ module Nuntius
|
|
|
53
53
|
private
|
|
54
54
|
|
|
55
55
|
def process_delivery
|
|
56
|
-
message.
|
|
56
|
+
message.delivered
|
|
57
57
|
message.metadata[:feedback] = {type: "delivery", info: context.notification["delivery"]}
|
|
58
58
|
message.save!
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def process_bounce
|
|
62
|
-
message.
|
|
62
|
+
message.bounced
|
|
63
63
|
message.metadata[:feedback] = {type: "bounce", info: context.notification["bounce"]}
|
|
64
64
|
message.save!
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
def process_complaint
|
|
68
|
-
message.
|
|
68
|
+
message.complaint
|
|
69
69
|
message.metadata[:feedback] = {type: "complaint", info: context.notification["complaint"]}
|
|
70
70
|
message.save!
|
|
71
71
|
end
|
|
@@ -24,10 +24,12 @@
|
|
|
24
24
|
.grid.grid-cols-12.gap-4 data-toggle-target="insertion"
|
|
25
25
|
|
|
26
26
|
template data-toggle-target='toggleable' data-toggle-value='mail'
|
|
27
|
-
.col-span-
|
|
27
|
+
.col-span-4
|
|
28
28
|
= f.input :link_tracking, as: :switch
|
|
29
|
-
.col-span-
|
|
29
|
+
.col-span-4
|
|
30
30
|
= f.input :open_tracking, as: :switch
|
|
31
|
+
.col-span-4
|
|
32
|
+
= f.input :publish_at, as: :date_time
|
|
31
33
|
.col-span-12
|
|
32
34
|
= f.input :subject
|
|
33
35
|
.col-span-12
|
|
@@ -51,7 +53,7 @@
|
|
|
51
53
|
= @campaign.messages.where(status: "sent").count
|
|
52
54
|
.flex.flex-col.bg-gray-200.p-8
|
|
53
55
|
dt.text-sm.font-semibold.leading-6.text-gray-600
|
|
54
|
-
= t(".messages_not_sent")
|
|
56
|
+
= t(".messages_not_sent")
|
|
55
57
|
dd.order-first.text-3xl.font-semibold.tracking-tight.text-gray-900
|
|
56
58
|
= @campaign.messages.where.not(status: "sent").count
|
|
57
59
|
.flex.flex-col.bg-gray-200.p-8
|
|
@@ -64,6 +66,6 @@
|
|
|
64
66
|
= t(".messages_clicked")
|
|
65
67
|
dd.order-first.text-3xl.font-semibold.tracking-tight.text-gray-900
|
|
66
68
|
= @campaign.messages.where("click_count >= 1").count
|
|
67
|
-
|
|
69
|
+
|
|
68
70
|
- card.with_tab :messages, padding: false do |tab|
|
|
69
71
|
= sts.table :"nuntius/campaign_messages", params: { campaign_id: @campaign.id }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
= form_with url: import_admin_list_subscribers_path(@list), multipart: true do |f|
|
|
2
|
+
= sts.card :nuntius_admin_lists_subscribers_import, title: t('.title'), icon: 'fad fa-file-import' do |card|
|
|
3
|
+
- card.with_action
|
|
4
|
+
= f.submit t('.import'), class: 'btn btn-primary'
|
|
5
|
+
|
|
6
|
+
.grid.grid-cols-12.gap-4.p-4
|
|
7
|
+
.col-span-12
|
|
8
|
+
p.text-sm.text-gray-600= t('.hint')
|
|
9
|
+
|
|
10
|
+
.col-span-12
|
|
11
|
+
label.block.text-sm.font-medium.mb-1= t('.file_label')
|
|
12
|
+
= f.file_field :file, accept: '.csv', class: 'block w-full text-sm'
|
|
13
|
+
|
|
14
|
+
.col-span-12
|
|
15
|
+
p.text-xs.text-gray-500= t('.columns_hint')
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
- card.with_tab:details, padding: true
|
|
28
28
|
= sts.info class: "grid grid-cols-1 gap-4 sm:grid-cols-3" do |info|
|
|
29
|
-
= info.with_item :
|
|
29
|
+
= info.with_item :state, content: @message.state, class: "sm:col-span-1"
|
|
30
30
|
= info.with_item :transport, content: @message.transport, class: "sm:col-span-1"
|
|
31
31
|
= info.with_item :provider, content: @message.provider, class: "sm:col-span-1"
|
|
32
32
|
= info.with_item :provider_id, content: @message.provider_id, class: "sm:col-span-1"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
= sts.form_for(@subscriber, url: @subscriber.new_record? ? subscribers_path(@list) : subscriber_path(@subscriber), html: {multipart: true}) do |f|
|
|
2
|
+
= f.hidden_field :list_id
|
|
3
|
+
= sts.card :nuntius_admin_lists_subscribers, title: t('.subscribe_to_list', list: @subscriber.list.name), icon: 'fad fa-address-card' do |card|
|
|
4
|
+
- card.with_action
|
|
5
|
+
= f.button class: 'button primary' do
|
|
6
|
+
- if @subscriber.new_record?
|
|
7
|
+
= t('.create')
|
|
8
|
+
- else
|
|
9
|
+
= t('.update')
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
.grid.grid-cols-12.gap-4
|
|
13
|
+
.col-span-6
|
|
14
|
+
= f.input :first_name
|
|
15
|
+
.col-span-6
|
|
16
|
+
= f.input :last_name
|
|
17
|
+
.col-span-12
|
|
18
|
+
= f.input :phone_number
|
|
19
|
+
.col-span-12
|
|
20
|
+
= f.input :email
|
data/config/locales/en.yml
CHANGED
|
@@ -47,6 +47,7 @@ en:
|
|
|
47
47
|
lists:
|
|
48
48
|
new: New list
|
|
49
49
|
new_subscriber: New subscriber
|
|
50
|
+
import_subscribers: Import subscribers
|
|
50
51
|
locales:
|
|
51
52
|
new: New locale
|
|
52
53
|
templates:
|
|
@@ -113,6 +114,16 @@ en:
|
|
|
113
114
|
edit:
|
|
114
115
|
metadata: Metadata
|
|
115
116
|
subscribers: Subscribers
|
|
117
|
+
import:
|
|
118
|
+
title: Import subscribers
|
|
119
|
+
file_label: CSV file
|
|
120
|
+
import: Import
|
|
121
|
+
hint: "Upload a CSV file to bulk import subscribers into this list. The first row must contain column headers."
|
|
122
|
+
columns_hint: "Supported columns: first_name, last_name, email, phone_number. Any additional columns will be stored in the subscriber's metadata."
|
|
123
|
+
queued: "Import started, you will be notified when it completes."
|
|
124
|
+
no_file: Please select a CSV file to import.
|
|
125
|
+
invalid_csv: "Invalid CSV file: %{message}"
|
|
126
|
+
success: "Import complete: %{imported} subscriber(s) imported, %{failed} failed."
|
|
116
127
|
locales:
|
|
117
128
|
edit:
|
|
118
129
|
card:
|
|
@@ -180,6 +191,10 @@ en:
|
|
|
180
191
|
Nuntius templates: Templates
|
|
181
192
|
title: Templates
|
|
182
193
|
subscribers:
|
|
194
|
+
edit:
|
|
195
|
+
subscribe_to_list: Subscribe to %{list}
|
|
196
|
+
create: Subscribe
|
|
197
|
+
update: Update
|
|
183
198
|
show:
|
|
184
199
|
unsubscribe: Unsubscribe
|
|
185
200
|
resubscribe: Resubscribe
|
data/config/locales/nl.yml
CHANGED
|
@@ -42,6 +42,7 @@ nl:
|
|
|
42
42
|
click_count: Aantal geklikt
|
|
43
43
|
campaign_id: Campagne
|
|
44
44
|
created_at: Aangemaakt om
|
|
45
|
+
state: Status
|
|
45
46
|
models:
|
|
46
47
|
campaign: Campagne
|
|
47
48
|
layout: Layout
|
|
@@ -59,6 +60,7 @@ nl:
|
|
|
59
60
|
lists:
|
|
60
61
|
new: Nieuwe lijst
|
|
61
62
|
new_subscriber: Nieuwe abonnee
|
|
63
|
+
import_subscribers: Importeer abonnees
|
|
62
64
|
locales:
|
|
63
65
|
new: Nieuwe locale
|
|
64
66
|
templates:
|
|
@@ -126,6 +128,16 @@ nl:
|
|
|
126
128
|
edit:
|
|
127
129
|
metadata: Metadata
|
|
128
130
|
subscribers: Abonnees
|
|
131
|
+
import:
|
|
132
|
+
title: Abonnees importeren
|
|
133
|
+
file_label: CSV-bestand
|
|
134
|
+
import: Importeer
|
|
135
|
+
no_file: Geen bestand geselecteerd
|
|
136
|
+
invalid_csv: "Ongeldig CSV-bestand: %{message}"
|
|
137
|
+
queued: "Import gestart, je ontvangt een melding zodra de import klaar is."
|
|
138
|
+
success: "%{imported} abonnee(s) geïmporteerd, %{failed} mislukt"
|
|
139
|
+
hint: Upload een CSV-bestand om abonnees in bulk te importeren. De eerste rij moet de kolomkoppen bevatten.
|
|
140
|
+
columns_hint: "Ondersteunde kolommen: first_name, last_name, email, phone_number. Extra kolommen worden opgeslagen in metadata."
|
|
129
141
|
locales:
|
|
130
142
|
edit:
|
|
131
143
|
card:
|
|
@@ -163,7 +175,7 @@ nl:
|
|
|
163
175
|
provider_id: Provider ID
|
|
164
176
|
refreshes: Refreshes
|
|
165
177
|
request_id: Request ID
|
|
166
|
-
|
|
178
|
+
state: Status
|
|
167
179
|
subject: Onderwerp
|
|
168
180
|
to: Aan
|
|
169
181
|
transport: Transport
|
|
@@ -195,6 +207,10 @@ nl:
|
|
|
195
207
|
Nuntius templates: Templates
|
|
196
208
|
title: Templates
|
|
197
209
|
subscribers:
|
|
210
|
+
edit:
|
|
211
|
+
subscribe_to_list: Abbonnement op %{list}
|
|
212
|
+
create: Abbonneren
|
|
213
|
+
update: Aanpassen
|
|
198
214
|
show:
|
|
199
215
|
unsubscribe: Uitschrijven
|
|
200
216
|
resubscribe: Opnieuw inschrijven
|
data/config/routes.rb
CHANGED
|
@@ -41,7 +41,12 @@ Nuntius::Engine.routes.draw do
|
|
|
41
41
|
resources :attachments, controller: "satis/attachments"
|
|
42
42
|
end
|
|
43
43
|
resources :lists do
|
|
44
|
-
resources :subscribers, controller: "lists/subscribers"
|
|
44
|
+
resources :subscribers, controller: "lists/subscribers" do
|
|
45
|
+
collection do
|
|
46
|
+
get :import
|
|
47
|
+
post :import
|
|
48
|
+
end
|
|
49
|
+
end
|
|
45
50
|
end
|
|
46
51
|
resources :messages do
|
|
47
52
|
member do
|
data/lib/nuntius/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nuntius
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tom de Grunt
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: apnotic
|
|
@@ -442,7 +442,9 @@ files:
|
|
|
442
442
|
- app/exceptions/nuntius/missing_messenger_exception.rb
|
|
443
443
|
- app/helpers/nuntius/application_helper.rb
|
|
444
444
|
- app/jobs/nuntius/application_job.rb
|
|
445
|
+
- app/jobs/nuntius/campaign_publish_job.rb
|
|
445
446
|
- app/jobs/nuntius/deliver_inbound_message_job.rb
|
|
447
|
+
- app/jobs/nuntius/import_subscribers_job.rb
|
|
446
448
|
- app/jobs/nuntius/messenger_job.rb
|
|
447
449
|
- app/jobs/nuntius/purge_message_job.rb
|
|
448
450
|
- app/jobs/nuntius/retrieve_mail_job.rb
|
|
@@ -452,6 +454,7 @@ files:
|
|
|
452
454
|
- app/message_boxes/nuntius/base_message_box.rb
|
|
453
455
|
- app/messengers/nuntius/base_messenger.rb
|
|
454
456
|
- app/messengers/nuntius/custom_messenger.rb
|
|
457
|
+
- app/messengers/nuntius/message_messenger.rb
|
|
455
458
|
- app/models/nuntius/application_record.rb
|
|
456
459
|
- app/models/nuntius/attachment.rb
|
|
457
460
|
- app/models/nuntius/campaign.rb
|
|
@@ -511,6 +514,7 @@ files:
|
|
|
511
514
|
- app/views/nuntius/admin/lists/edit.html.slim
|
|
512
515
|
- app/views/nuntius/admin/lists/index.html.slim
|
|
513
516
|
- app/views/nuntius/admin/lists/subscribers/edit.html.slim
|
|
517
|
+
- app/views/nuntius/admin/lists/subscribers/import.html.slim
|
|
514
518
|
- app/views/nuntius/admin/locales/edit.html.slim
|
|
515
519
|
- app/views/nuntius/admin/locales/index.html.slim
|
|
516
520
|
- app/views/nuntius/admin/messages/index.html.slim
|
|
@@ -519,6 +523,7 @@ files:
|
|
|
519
523
|
- app/views/nuntius/admin/templates/index.html.slim
|
|
520
524
|
- app/views/nuntius/dashboard/show.html.slim
|
|
521
525
|
- app/views/nuntius/messages/show.html.slim
|
|
526
|
+
- app/views/nuntius/subscribers/edit.html.slim
|
|
522
527
|
- app/views/nuntius/subscribers/show.html.slim
|
|
523
528
|
- config/locales/en.yml
|
|
524
529
|
- config/locales/nl.yml
|
|
@@ -554,6 +559,8 @@ files:
|
|
|
554
559
|
- db/migrate/20260210122500_add_tracking_to_nuntius_messages.rb
|
|
555
560
|
- db/migrate/20260225123822_add_metadata_to_subscriber.rb
|
|
556
561
|
- db/migrate/20260310153208_add_subscriber_to_message.rb
|
|
562
|
+
- db/migrate/20260324140324_add_publish_at_to_campaign.rb
|
|
563
|
+
- db/migrate/20260324150714_rename_status_to_state.rb
|
|
557
564
|
- lib/generators/nuntius/install_generator.rb
|
|
558
565
|
- lib/generators/nuntius/tailwind_config_generator.rb
|
|
559
566
|
- lib/generators/nuntius/templates/config/initializers/nuntius.rb
|