nuntius 1.0.27 → 1.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +77 -18
- data/Rakefile +16 -39
- data/app/controllers/nuntius/admin/campaigns_controller.rb +12 -10
- data/app/controllers/nuntius/admin/layouts/attachments_controller.rb +2 -1
- data/app/controllers/nuntius/admin/layouts_controller.rb +1 -1
- data/app/controllers/nuntius/admin/lists/subscribers_controller.rb +1 -1
- data/app/controllers/nuntius/admin/lists_controller.rb +4 -4
- data/app/controllers/nuntius/admin/locales_controller.rb +3 -2
- data/app/controllers/nuntius/admin/messages_controller.rb +2 -2
- data/app/controllers/nuntius/admin/templates_controller.rb +1 -1
- data/app/controllers/nuntius/api/events_controller.rb +2 -2
- data/app/controllers/nuntius/application_admin_controller.rb +1 -1
- data/app/controllers/nuntius/callbacks_controller.rb +4 -4
- data/app/controllers/nuntius/dashboard_controller.rb +1 -1
- data/app/controllers/nuntius/feedback_controller.rb +1 -2
- data/app/controllers/nuntius/inbound_messages/twilio_inbound_smses_controller.rb +7 -7
- data/app/controllers/nuntius/messages_controller.rb +1 -1
- data/app/controllers/nuntius/subscribers_controller.rb +33 -0
- data/app/drops/nuntius/list_drop.rb +7 -0
- data/app/drops/nuntius/subscriber_drop.rb +7 -0
- data/app/exceptions/nuntius/base_exception.rb +2 -0
- data/app/exceptions/nuntius/missing_messenger_exception.rb +2 -0
- data/app/helpers/nuntius/application_helper.rb +6 -11
- data/app/jobs/nuntius/deliver_inbound_message_job.rb +1 -1
- data/app/jobs/nuntius/purge_message_job.rb +2 -2
- data/app/jobs/nuntius/retrieve_mail_job.rb +2 -2
- data/app/jobs/nuntius/transport_delivery_job.rb +2 -2
- data/app/message_boxes/nuntius/base_message_box.rb +46 -23
- data/app/messengers/nuntius/base_messenger.rb +13 -12
- data/app/models/nuntius/application_record.rb +1 -1
- data/app/models/nuntius/attachment.rb +4 -2
- data/app/models/nuntius/campaign.rb +4 -51
- data/app/models/nuntius/concerns/yamlify.rb +2 -2
- data/app/models/nuntius/inbound_message.rb +2 -0
- data/app/models/nuntius/list.rb +11 -1
- data/app/models/nuntius/message.rb +34 -29
- data/app/models/nuntius/subscriber.rb +21 -1
- data/app/models/nuntius/template.rb +20 -20
- data/app/presenters/application_presenter.rb +1 -1
- data/app/presenters/template_presenter.rb +3 -3
- data/app/providers/nuntius/apnotic_push_provider.rb +17 -17
- data/app/providers/nuntius/base_provider.rb +6 -6
- data/app/providers/nuntius/firebase_push_provider.rb +8 -8
- data/app/providers/nuntius/houston_push_provider.rb +15 -15
- data/app/providers/nuntius/message_bird_sms_provider.rb +7 -7
- data/app/providers/nuntius/slack_slack_provider.rb +7 -7
- data/app/providers/nuntius/smtp_mail_provider.rb +36 -28
- data/app/providers/nuntius/teams_teams_provider.rb +24 -0
- data/app/providers/nuntius/twilio_sms_provider.rb +4 -4
- data/app/providers/nuntius/twilio_voice_provider.rb +13 -13
- data/app/services/nuntius/aws_sns_processor_service.rb +11 -11
- data/app/services/nuntius/deliver_campaign_service.rb +65 -0
- data/app/services/nuntius/deliver_inbound_message_service.rb +3 -2
- data/app/services/nuntius/retrieve_inbound_mail_service.rb +23 -16
- data/app/tables/nuntius_campaigns_table.rb +10 -1
- data/app/tables/nuntius_layouts_table.rb +4 -4
- data/app/tables/nuntius_lists_table.rb +1 -1
- data/app/tables/nuntius_locales_table.rb +1 -1
- data/app/tables/nuntius_messages_table.rb +5 -5
- data/app/tables/nuntius_subscribers_table.rb +3 -2
- data/app/tables/nuntius_templates_table.rb +12 -5
- data/app/transports/nuntius/base_transport.rb +1 -1
- data/app/transports/nuntius/mail_transport.rb +1 -1
- data/app/transports/nuntius/push_transport.rb +1 -1
- data/app/transports/nuntius/teams_transport.rb +6 -0
- data/app/views/nuntius/admin/campaigns/edit.html.slim +12 -16
- data/app/views/nuntius/admin/campaigns/index.html.slim +2 -2
- data/app/views/nuntius/admin/layouts/attachments/create.json.jbuilder +3 -3
- data/app/views/nuntius/admin/layouts/edit.html.slim +18 -19
- data/app/views/nuntius/admin/layouts/index.html.slim +2 -2
- data/app/views/nuntius/admin/lists/edit.html.slim +19 -9
- data/app/views/nuntius/admin/lists/index.html.slim +2 -2
- data/app/views/nuntius/admin/lists/subscribers/edit.html.slim +2 -2
- data/app/views/nuntius/admin/locales/edit.html.slim +3 -3
- data/app/views/nuntius/admin/locales/index.html.slim +2 -2
- data/app/views/nuntius/admin/messages/index.html.slim +2 -2
- data/app/views/nuntius/admin/messages/show.html.slim +12 -12
- data/app/views/nuntius/admin/templates/edit.html.slim +62 -64
- data/app/views/nuntius/admin/templates/index.html.slim +2 -2
- data/app/views/nuntius/dashboard/show.html.slim +2 -6
- data/app/views/nuntius/subscribers/show.html.slim +9 -0
- data/config/locales/en.yml +97 -1
- data/config/routes.rb +18 -8
- data/db/migrate/20190301201541_create_nuntius_templates.rb +5 -5
- data/db/migrate/20190301202436_create_nuntius_messages.rb +4 -4
- data/db/migrate/20190322114340_create_nuntius_campaigns.rb +2 -2
- data/db/migrate/20190322121338_create_nuntius_subscribers.rb +1 -1
- data/db/migrate/20190327124407_create_nuntius_layouts.rb +5 -5
- data/db/migrate/20190327143921_add_campaign_to_message.rb +1 -1
- data/db/migrate/20190417125153_change_message_status_default.rb +2 -2
- data/db/migrate/20190825080757_update_nuntius_message_sending_to_sent.rb +2 -2
- data/db/migrate/20200220154927_change_nuntius_templates_payload_type.rb +1 -1
- data/db/migrate/20200318095339_create_nuntius_attachments.rb +2 -2
- data/db/migrate/20201121185718_create_nuntius_inbound_messages.rb +1 -1
- data/db/migrate/20240205204719_add_description_to_list.rb +5 -0
- data/db/migrate/20240206203019_add_slug_to_nuntius_list.rb +9 -0
- data/lib/nuntius/active_record_helpers.rb +4 -4
- data/lib/nuntius/configuration.rb +64 -27
- data/lib/nuntius/deprecator.rb +2 -0
- data/lib/nuntius/devise.rb +1 -1
- data/lib/nuntius/engine.rb +13 -27
- data/lib/nuntius/i18n_store.rb +6 -5
- data/lib/nuntius/liquid/tags/attach_tag.rb +11 -11
- data/lib/nuntius/mail_allow_list.rb +2 -2
- data/lib/nuntius/nuntiable.rb +3 -1
- data/lib/nuntius/transactio.rb +1 -1
- data/lib/nuntius/version.rb +1 -1
- data/lib/nuntius.rb +13 -27
- data/lib/preamble.rb +18 -18
- data/lib/tasks/nuntius_tasks.rake +4 -4
- metadata +48 -33
- data/app/jobs/nuntius/process_inbound_message_job.rb +0 -10
- data/app/reportlets/nuntius/application_reportlet.rb +0 -35
- data/app/reportlets/nuntius/daily_messages_per_template_reportlet.rb +0 -50
- data/config/webpack/development.js +0 -5
- data/config/webpack/environment.js +0 -4
- data/config/webpack/production.js +0 -5
- data/config/webpack/test.js +0 -5
- data/config/webpacker.yml +0 -119
@@ -13,10 +13,10 @@ module Nuntius
|
|
13
13
|
@all_settings ||= []
|
14
14
|
end
|
15
15
|
|
16
|
-
def setting_reader(name, required: false, default: nil, description:
|
16
|
+
def setting_reader(name, required: false, default: nil, description: "")
|
17
17
|
@all_settings ||= []
|
18
18
|
@all_settings.push(name: name, required: required, default: default, description: description)
|
19
|
-
define_method(name) { required ? settings.fetch(name) : settings
|
19
|
+
define_method(name) { required ? settings.fetch(name) : settings[name] || default }
|
20
20
|
end
|
21
21
|
|
22
22
|
def transport(transport = nil)
|
@@ -31,7 +31,7 @@ module Nuntius
|
|
31
31
|
|
32
32
|
def class_from_name(name, transport)
|
33
33
|
Nuntius.const_get("#{name}_#{transport}_provider".camelize)
|
34
|
-
rescue
|
34
|
+
rescue
|
35
35
|
nil
|
36
36
|
end
|
37
37
|
end
|
@@ -48,17 +48,17 @@ module Nuntius
|
|
48
48
|
|
49
49
|
# Override this in implementation
|
50
50
|
def callback(_params)
|
51
|
-
[404, {
|
51
|
+
[404, {"Content-Type" => "text/html; charset=utf-8"}, ["Not found"]]
|
52
52
|
end
|
53
53
|
|
54
54
|
def name
|
55
|
-
self.class.name.demodulize.underscore.gsub(/_#{self.class.transport}_provider$/,
|
55
|
+
self.class.name.demodulize.underscore.gsub(/_#{self.class.transport}_provider$/, "").to_sym
|
56
56
|
end
|
57
57
|
|
58
58
|
private
|
59
59
|
|
60
60
|
def translated_status(status)
|
61
|
-
self.class.states.find { |key, _value| key.is_a?(Array) ? key.include?(status) : key == status }&.last ||
|
61
|
+
self.class.states.find { |key, _value| key.is_a?(Array) ? key.include?(status) : key == status }&.last || "sent"
|
62
62
|
end
|
63
63
|
|
64
64
|
def settings
|
@@ -1,24 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "fcm"
|
4
4
|
|
5
5
|
module Nuntius
|
6
6
|
class FirebasePushProvider < BaseProvider
|
7
7
|
transport :push
|
8
8
|
|
9
|
-
setting_reader :server_key, required: true, description:
|
9
|
+
setting_reader :server_key, required: true, description: "Server key for the project, see Firebase console"
|
10
10
|
|
11
11
|
def deliver
|
12
12
|
fcm = FCM.new(server_key)
|
13
13
|
|
14
|
-
options = (message.payload || {}).merge(data: {
|
14
|
+
options = (message.payload || {}).merge(data: {body: message.text})
|
15
15
|
response = fcm.send([message.to], options)
|
16
16
|
|
17
|
-
message.status = if response[:status_code] != 200 || response[:response] !=
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
message.status = if response[:status_code] != 200 || response[:response] != "success"
|
18
|
+
"undelivered"
|
19
|
+
else
|
20
|
+
"sent"
|
21
|
+
end
|
22
22
|
|
23
23
|
message
|
24
24
|
end
|
@@ -1,39 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "houston"
|
4
4
|
|
5
5
|
module Nuntius
|
6
6
|
class HoustonPushProvider < BaseProvider
|
7
7
|
transport :push
|
8
8
|
|
9
9
|
setting_reader :certificate,
|
10
|
-
|
11
|
-
|
10
|
+
required: true,
|
11
|
+
description: "The contents of a valid APNS push certificate in .pem format"
|
12
12
|
setting_reader :passphrase,
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
required: false,
|
14
|
+
description: "If the APNS certificate is protected by a passphrase, " \
|
15
|
+
"provide this variable to use when decrypting it."
|
16
16
|
setting_reader :environment,
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
required: false,
|
18
|
+
default: :production,
|
19
|
+
description: "Development or production, defaults to production"
|
20
20
|
|
21
21
|
def deliver
|
22
22
|
return message if message.to.size != 64
|
23
23
|
|
24
|
-
apn = environment.to_sym == :development ? Houston::Client.development : Houston::Client.production
|
24
|
+
apn = (environment.to_sym == :development) ? Houston::Client.development : Houston::Client.production
|
25
25
|
apn.certificate = certificate
|
26
26
|
apn.passphrase = passphrase
|
27
27
|
|
28
28
|
notification = Houston::Notification.new((message.payload || {}).merge(device: message.to,
|
29
|
-
|
29
|
+
alert: message.text, sound: "default"))
|
30
30
|
apn.push(notification)
|
31
31
|
|
32
32
|
message.status = if notification.sent?
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
"sent"
|
34
|
+
elsif !notification.valid? || notification.error
|
35
|
+
"undelivered"
|
36
|
+
end
|
37
37
|
message
|
38
38
|
end
|
39
39
|
end
|
@@ -1,32 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "messagebird"
|
4
4
|
|
5
5
|
module Nuntius
|
6
6
|
# Send SMS messages using messagebird.com
|
7
7
|
class MessageBirdSmsProvider < BaseProvider
|
8
8
|
transport :sms
|
9
9
|
|
10
|
-
setting_reader :auth_token, required: true, description:
|
10
|
+
setting_reader :auth_token, required: true, description: "Authentication token"
|
11
11
|
setting_reader :from, required: true, description: "Phone-number or name (say: 'Nuntius') to send the message from"
|
12
12
|
|
13
13
|
# Messagebird statusses: scheduled, sent, buffered, delivered, expired, and delivery_failed.
|
14
|
-
states %w[expired delivery_failed] =>
|
14
|
+
states %w[expired delivery_failed] => "undelivered", "delivered" => "delivered"
|
15
15
|
|
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.status = translated_status(response.recipients[
|
19
|
+
message.status = 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.status = translated_status(response.recipients[
|
27
|
-
Nuntius.logger.info "SMS #{message.to} status: #{message.status}"
|
26
|
+
message.status = translated_status(response.recipients["items"].first.status)
|
27
|
+
Nuntius.config.logger.call.info "SMS #{message.to} status: #{message.status}"
|
28
28
|
message
|
29
|
-
rescue
|
29
|
+
rescue => _e
|
30
30
|
message
|
31
31
|
end
|
32
32
|
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "slack"
|
4
4
|
|
5
5
|
module Nuntius
|
6
6
|
class SlackSlackProvider < BaseProvider
|
7
7
|
transport :slack
|
8
8
|
|
9
|
-
setting_reader :api_key, required: true, description:
|
9
|
+
setting_reader :api_key, required: true, description: "API key for the Slack workspace"
|
10
10
|
|
11
11
|
def deliver
|
12
12
|
client = Slack::Web::Client.new(token: api_key)
|
@@ -24,11 +24,11 @@ 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
|
-
message.status = if response[
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
message.status = if response["ok"]
|
28
|
+
"sent"
|
29
|
+
else
|
30
|
+
"undelivered"
|
31
|
+
end
|
32
32
|
|
33
33
|
message
|
34
34
|
end
|
@@ -1,80 +1,88 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "mail"
|
4
4
|
|
5
5
|
module Nuntius
|
6
6
|
class SmtpMailProvider < BaseProvider
|
7
7
|
transport :mail
|
8
8
|
|
9
|
-
setting_reader :from_header, required: true, description:
|
10
|
-
setting_reader :host, required: true, description:
|
11
|
-
setting_reader :port, required: true, description:
|
12
|
-
setting_reader :username, required: true, description:
|
13
|
-
setting_reader :password, required: true, description:
|
14
|
-
setting_reader :allow_list, required: false, default: [], description:
|
9
|
+
setting_reader :from_header, required: true, description: "From header (example: Nuntius Messenger <nuntius@entdec.com>)"
|
10
|
+
setting_reader :host, required: true, description: "Host (example: smtp.soverin.net)"
|
11
|
+
setting_reader :port, required: true, description: "Port (example: 578)"
|
12
|
+
setting_reader :username, required: true, description: "Username (nuntius@entdec.com)"
|
13
|
+
setting_reader :password, required: true, description: "Password"
|
14
|
+
setting_reader :allow_list, required: false, default: [], description: "Allow list (example: [boxture.com, tom@degrunt.net])"
|
15
15
|
|
16
16
|
def deliver
|
17
17
|
return block unless MailAllowList.new(settings[:allow_list]).allowed?(message.to)
|
18
18
|
return block if Nuntius::Message.where(status: %w[complaint bounced], to: message.to).count >= 1
|
19
19
|
|
20
20
|
mail = if message.from.present?
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
Mail.new(sender: from_header, from: message.from)
|
22
|
+
else
|
23
|
+
Mail.new(from: from_header)
|
24
|
+
end
|
25
25
|
|
26
26
|
if Rails.env.test?
|
27
27
|
mail.delivery_method :test
|
28
28
|
else
|
29
29
|
mail.delivery_method :smtp,
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
30
|
+
address: host,
|
31
|
+
port: port,
|
32
|
+
user_name: username,
|
33
|
+
password: password,
|
34
|
+
return_response: true
|
35
35
|
end
|
36
36
|
|
37
37
|
mail.to = message.to
|
38
38
|
mail.subject = message.subject
|
39
|
-
mail.part content_type:
|
39
|
+
mail.part content_type: "multipart/alternative" do |p|
|
40
40
|
p.text_part = Mail::Part.new(
|
41
41
|
body: message.text,
|
42
|
-
content_type:
|
43
|
-
charset:
|
42
|
+
content_type: "text/plain",
|
43
|
+
charset: "UTF-8"
|
44
44
|
)
|
45
45
|
if message.html.present?
|
46
|
-
message.html = message.html.gsub(
|
46
|
+
message.html = message.html.gsub("%7B%7Bmessage_url%7D%7D") { message_url(message) }
|
47
47
|
p.html_part = Mail::Part.new(
|
48
48
|
body: message.html,
|
49
|
-
content_type:
|
50
|
-
charset:
|
49
|
+
content_type: "text/html",
|
50
|
+
charset: "UTF-8"
|
51
51
|
)
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
55
|
message.attachments.each do |attachment|
|
56
|
-
mail.attachments[attachment.filename.to_s] = {
|
56
|
+
mail.attachments[attachment.filename.to_s] = {mime_type: attachment.content_type, content: attachment.download}
|
57
57
|
end
|
58
58
|
|
59
|
-
|
59
|
+
begin
|
60
|
+
response = mail.deliver!
|
61
|
+
rescue Net::SMTPFatalError
|
62
|
+
message.status = "rejected"
|
63
|
+
return message
|
64
|
+
rescue Net::SMTPServerBusy, Net::ReadTimeout
|
65
|
+
message.status = "undelivered"
|
66
|
+
return message
|
67
|
+
end
|
60
68
|
|
61
69
|
message.provider_id = mail.message_id
|
62
|
-
message.status =
|
63
|
-
message.status =
|
70
|
+
message.status = "undelivered"
|
71
|
+
message.status = "sent" if Rails.env.test? ? true : response.success?
|
64
72
|
message.last_sent_at = Time.zone.now if message.sent?
|
65
73
|
|
66
74
|
message
|
67
75
|
end
|
68
76
|
|
69
77
|
def block
|
70
|
-
message.status =
|
78
|
+
message.status = "blocked"
|
71
79
|
message
|
72
80
|
end
|
73
81
|
|
74
82
|
private
|
75
83
|
|
76
84
|
def message_url(message)
|
77
|
-
Nuntius::Engine.routes.url_helpers.message_url(message.id, host: Nuntius.config.host(message))
|
85
|
+
Nuntius::Engine.routes.url_helpers.message_url(message.id, host: Nuntius.config.host.call(message))
|
78
86
|
end
|
79
87
|
end
|
80
88
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nuntius
|
4
|
+
class TeamsTeamsProvider < BaseProvider
|
5
|
+
transport :teams
|
6
|
+
|
7
|
+
def deliver
|
8
|
+
# NOTE: Attachments are not supported
|
9
|
+
# https://learn.microsoft.com/en-us/power-automate/overview-adaptive-cards
|
10
|
+
# https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook?tabs=newteams%2Cdotnet
|
11
|
+
|
12
|
+
args = (message.payload || {}).merge(text: message.text)
|
13
|
+
response = Faraday.post(message[:to], JSON.dump(args), {"Content-Type": "application/json"})
|
14
|
+
|
15
|
+
message.status = if response.status == 200
|
16
|
+
"sent"
|
17
|
+
else
|
18
|
+
"undelivered"
|
19
|
+
end
|
20
|
+
|
21
|
+
message
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,18 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "twilio-ruby"
|
4
4
|
|
5
5
|
module Nuntius
|
6
6
|
# Send SMS messages using twilio.com
|
7
7
|
class TwilioSmsProvider < BaseProvider
|
8
8
|
transport :sms
|
9
9
|
|
10
|
-
setting_reader :auth_token, required: true, description:
|
11
|
-
setting_reader :sid, required: true, description:
|
10
|
+
setting_reader :auth_token, required: true, description: "Authentication token"
|
11
|
+
setting_reader :sid, required: true, description: "Application SID, see Twilio console"
|
12
12
|
setting_reader :from, required: true, description: "Phone-number or name (example: 'Nuntius') to send the message from"
|
13
13
|
|
14
14
|
# Twilio statusses: queued, failed, sent, delivered, or undelivered
|
15
|
-
states %w[failed undelivered] =>
|
15
|
+
states %w[failed undelivered] => "undelivered", "delivered" => "delivered"
|
16
16
|
|
17
17
|
def deliver
|
18
18
|
response = client.messages.create(from: message.from.present? ? message.from : from, to: message.to, body: message.text)
|
@@ -1,23 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "twilio-ruby"
|
4
4
|
|
5
5
|
module Nuntius
|
6
6
|
# Send Voice call messages using twilio.com
|
7
7
|
class TwilioVoiceProvider < BaseProvider
|
8
8
|
transport :voice
|
9
9
|
|
10
|
-
setting_reader :host, required: true, description:
|
11
|
-
setting_reader :auth_token, required: true, description:
|
12
|
-
setting_reader :sid, required: true, description:
|
10
|
+
setting_reader :host, required: true, description: "Host or base-url for the application"
|
11
|
+
setting_reader :auth_token, required: true, description: "Authentication token"
|
12
|
+
setting_reader :sid, required: true, description: "Application SID, see Twilio console"
|
13
13
|
setting_reader :from, required: true, description: "Phone-number or name (example: 'Nuntius') to send the message from"
|
14
14
|
|
15
15
|
# Twilio statusses: queued, failed, sent, delivered, or undelivered
|
16
|
-
states %w[failed undelivered] =>
|
16
|
+
states %w[failed undelivered] => "undelivered", %w[delivered completed] => "delivered"
|
17
17
|
|
18
18
|
def deliver
|
19
19
|
# Need hostname here too
|
20
|
-
response = client.calls.create(from: message.from.present? ? message.from : from, to: message.to, method:
|
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
22
|
message.status = translated_status(response.status)
|
23
23
|
message
|
@@ -36,9 +36,9 @@ module Nuntius
|
|
36
36
|
twiml = script_for_path(message, "/#{params[:path]}", params)
|
37
37
|
|
38
38
|
if twiml
|
39
|
-
[200, {
|
39
|
+
[200, {"Content-Type" => "application/xml"}, [twiml[:body]]]
|
40
40
|
else
|
41
|
-
[404, {
|
41
|
+
[404, {"Content-Type" => "text/html; charset=utf-8"}, ["Not found"]]
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
@@ -48,16 +48,16 @@ module Nuntius
|
|
48
48
|
@client ||= Twilio::REST::Client.new(sid, auth_token)
|
49
49
|
end
|
50
50
|
|
51
|
-
def script_for_path(message, path =
|
51
|
+
def script_for_path(message, path = "/", _params)
|
52
52
|
scripts = message.text.delete("\r").split("\n\n")
|
53
53
|
|
54
54
|
scripts = scripts.map do |script|
|
55
55
|
preamble = Preamble.parse(script)
|
56
|
-
payload = preamble.metadata ?
|
57
|
-
payload = payload.gsub(
|
58
|
-
metadata = preamble.metadata || {
|
56
|
+
payload = preamble.metadata ? preamble.content : script
|
57
|
+
payload = payload.gsub("{{url}}", callback_url)
|
58
|
+
metadata = preamble.metadata || {path: "/"}
|
59
59
|
|
60
|
-
{
|
60
|
+
{headers: metadata.with_indifferent_access, body: payload}
|
61
61
|
end
|
62
62
|
|
63
63
|
scripts.find { |s| s[:headers][:path] == path }
|
@@ -20,13 +20,13 @@ module Nuntius
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def perform
|
23
|
-
if context.notification[
|
24
|
-
Nuntius.config.logger.info(
|
25
|
-
|
23
|
+
if context.notification["Type"] == "SubscriptionConfirmation"
|
24
|
+
Nuntius.config.logger.info("Confirming SNS subscription")
|
25
|
+
Faraday.get(body["SubscribeURL"])
|
26
26
|
return
|
27
27
|
end
|
28
28
|
|
29
|
-
type = context.notification[
|
29
|
+
type = context.notification["notificationType"]
|
30
30
|
|
31
31
|
unless message_id
|
32
32
|
Nuntius.config.logger.warn("SNS / SES message could not determine message id: #{context.notification}")
|
@@ -39,11 +39,11 @@ module Nuntius
|
|
39
39
|
Nuntius.config.logger.info("SNS /SES updating message #{message.id} for #{type}")
|
40
40
|
|
41
41
|
case type
|
42
|
-
when
|
42
|
+
when "Delivery"
|
43
43
|
process_delivery
|
44
|
-
when
|
44
|
+
when "Bounce"
|
45
45
|
process_bounce
|
46
|
-
when
|
46
|
+
when "Complaint"
|
47
47
|
process_complaint
|
48
48
|
else
|
49
49
|
false
|
@@ -54,24 +54,24 @@ module Nuntius
|
|
54
54
|
|
55
55
|
def process_delivery
|
56
56
|
message.status = :delivered
|
57
|
-
message.metadata[:feedback] = {
|
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
62
|
message.status = :bounced
|
63
|
-
message.metadata[:feedback] = {
|
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
68
|
message.status = :complaint
|
69
|
-
message.metadata[:feedback] = {
|
69
|
+
message.metadata[:feedback] = {type: "complaint", info: context.notification["complaint"]}
|
70
70
|
message.save!
|
71
71
|
end
|
72
72
|
|
73
73
|
def message_id
|
74
|
-
@message_id ||= context.notification.dig(
|
74
|
+
@message_id ||= context.notification.dig("mail", "commonHeaders", "messageId")&.[](1...-1)
|
75
75
|
end
|
76
76
|
|
77
77
|
def message
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nuntius
|
4
|
+
class DeliverCampaignService < ApplicationService
|
5
|
+
context do
|
6
|
+
attribute :campaign
|
7
|
+
validates :campaign, presence: true
|
8
|
+
end
|
9
|
+
|
10
|
+
delegate :campaign, to: :context
|
11
|
+
|
12
|
+
def perform
|
13
|
+
deliver
|
14
|
+
campaign.sent!
|
15
|
+
end
|
16
|
+
|
17
|
+
def deliver
|
18
|
+
transporter = BaseTransport.class_from_name(campaign.transport).new
|
19
|
+
campaign.list.subscribers.subscribed.each do |subscriber|
|
20
|
+
transporter.deliver(new_message(subscriber))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def new_message(subscriber, assigns = {})
|
25
|
+
assigns["subscriber"] = subscriber
|
26
|
+
if subscriber.nuntiable
|
27
|
+
name = Nuntius::BaseMessenger.liquid_variable_name_for(subscriber.nuntiable)
|
28
|
+
assigns[name] = subscriber.nuntiable
|
29
|
+
end
|
30
|
+
message = Nuntius::Message.new(transport: campaign.transport, campaign: campaign, nuntiable: subscriber.nuntiable, metadata: campaign.metadata)
|
31
|
+
|
32
|
+
locale_proc = Nuntius::BaseMessenger.messenger_for_obj(subscriber.nuntiable).locale
|
33
|
+
locale = instance_exec(subscriber.nuntiable, &locale_proc) if subscriber.nuntiable && locale_proc
|
34
|
+
|
35
|
+
message.from = render(:from, assigns, locale)
|
36
|
+
message.to = case campaign.transport
|
37
|
+
when "mail"
|
38
|
+
subscriber.email
|
39
|
+
when "sms"
|
40
|
+
subscriber.phone_number
|
41
|
+
when "voice"
|
42
|
+
subscriber.phone_number
|
43
|
+
end
|
44
|
+
|
45
|
+
message.subject = render(:subject, assigns, locale)
|
46
|
+
message.html = render(:html, assigns, locale, layout: campaign.layout&.data)
|
47
|
+
|
48
|
+
message
|
49
|
+
end
|
50
|
+
|
51
|
+
def translation_scope
|
52
|
+
scope = %w[nuntius]
|
53
|
+
scope << campaign.layout.name.underscore.tr(" ", "_") if layout
|
54
|
+
scope.join(".")
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def render(attr, assigns, locale, options = {})
|
60
|
+
I18n.with_locale(locale) do
|
61
|
+
::Liquidum.render(campaign.public_send(attr), {assigns: assigns.merge("campaign" => campaign), registers: {"campaign" => campaign}}.merge(options))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -7,8 +7,9 @@ module Nuntius
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def perform
|
10
|
-
context.inbound_message.update(status:
|
11
|
-
Nuntius::BaseMessageBox.deliver(context.inbound_message)
|
10
|
+
context.inbound_message.update!(status: "delivered")
|
11
|
+
result = Nuntius::BaseMessageBox.deliver(context.inbound_message)
|
12
|
+
context.inbound_message.update!(status: "processed") if result
|
12
13
|
end
|
13
14
|
end
|
14
15
|
end
|
@@ -8,27 +8,34 @@ module Nuntius
|
|
8
8
|
|
9
9
|
def perform
|
10
10
|
Mail::IMAP.new(context.settings).all do |message, imap, uid|
|
11
|
-
inbound_message = Nuntius::InboundMessage.
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
11
|
+
inbound_message = Nuntius::InboundMessage.find_or_initialize_by(transport: "mail", provider: "imap", provider_id: message.message_id)
|
12
|
+
if inbound_message.new_record?
|
13
|
+
inbound_message.digest = Digest::SHA256.hexdigest(message.to_s)
|
14
|
+
inbound_message.from = message.from
|
15
|
+
inbound_message.to = message.to
|
16
|
+
inbound_message.text = message.body
|
17
|
+
inbound_message.save!
|
18
18
|
|
19
|
-
if inbound_message.raw_message.attached?
|
20
|
-
if Digest::SHA256.hexdigest(message.to_s) == Digest::SHA256.hexdigest(inbound_message.raw_message.download)
|
21
|
-
# Only if we have an attachment and it's digest is the same as we find in the mailbox, delete!
|
22
|
-
# This never happens on the first round of fetching it.
|
23
|
-
imap.store(uid, '+FLAGS', [Net::IMAP::DELETED])
|
24
|
-
imap.expunge
|
25
|
-
end
|
26
|
-
else
|
27
19
|
si = StringIO.new
|
28
20
|
si.write(message.to_s)
|
29
21
|
si.rewind
|
30
22
|
inbound_message.raw_message.attach(io: si, filename: message.message_id)
|
31
|
-
|
23
|
+
|
24
|
+
elsif inbound_message.status == "processed" && Digest::SHA256.hexdigest(message.to_s) == Digest::SHA256.hexdigest(inbound_message.raw_message.download)
|
25
|
+
|
26
|
+
if context.settings[:delete_after_processing]
|
27
|
+
imap.uid_store(uid, "+FLAGS", [Net::IMAP::DELETED])
|
28
|
+
imap.expunge
|
29
|
+
else
|
30
|
+
existing_mailboxes = imap.list("", "*").map(&:name)
|
31
|
+
unless existing_mailboxes.include?("Archive")
|
32
|
+
imap.create("Archive")
|
33
|
+
end
|
34
|
+
imap.uid_copy(uid, "Archive")
|
35
|
+
|
36
|
+
imap.uid_store(uid, "+FLAGS", [Net::IMAP::DELETED])
|
37
|
+
imap.expunge
|
38
|
+
end
|
32
39
|
end
|
33
40
|
end
|
34
41
|
end
|
@@ -7,7 +7,15 @@ class NuntiusCampaignsTable < ActionTable::ActionTable
|
|
7
7
|
column(:metadata) { |campaign| Nuntius.config.metadata_humanize(campaign.metadata) }
|
8
8
|
column(:transport)
|
9
9
|
column(:state)
|
10
|
-
column(:list) { |campaign| campaign.list.name }
|
10
|
+
column(:list, sort_field: "nuntius_lists.name") { |campaign| campaign.list.name }
|
11
|
+
|
12
|
+
column :actions, title: "", sortable: false do |campaign|
|
13
|
+
next unless campaign.can_publish?
|
14
|
+
|
15
|
+
content_tag(:span, class: "btn-group btn-group-xs") do
|
16
|
+
concat link_to(content_tag(:i, nil, class: "fa fa-paper-plane"), nuntius.publish_admin_campaign_path(campaign), data: {turbo_confirm: "Are you sure you want to send out this campaign?", turbo_method: :post}, class: "btn btn-xs btn-danger")
|
17
|
+
end
|
18
|
+
end
|
11
19
|
|
12
20
|
initial_order :name, :asc
|
13
21
|
|
@@ -17,5 +25,6 @@ class NuntiusCampaignsTable < ActionTable::ActionTable
|
|
17
25
|
|
18
26
|
def scope
|
19
27
|
@scope = Nuntius::Campaign.visible
|
28
|
+
@scope = Nuntius::Campaign.joins(:list)
|
20
29
|
end
|
21
30
|
end
|