nuntius 1.4.7 → 1.4.9
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 +3 -0
- data/app/controllers/nuntius/admin/campaigns_controller.rb +2 -2
- data/app/controllers/nuntius/admin/lists_controller.rb +6 -4
- data/app/controllers/nuntius/admin/templates_controller.rb +1 -1
- data/app/controllers/nuntius/messages_controller.rb +47 -0
- data/app/controllers/nuntius/subscribers_controller.rb +4 -2
- data/app/models/nuntius/message.rb +18 -0
- data/app/models/nuntius/message_tracking.rb +7 -0
- data/app/models/nuntius/template.rb +2 -2
- data/app/providers/nuntius/smtp_mail_provider.rb +5 -1
- data/app/services/nuntius/deliver_campaign_service.rb +1 -1
- data/app/services/nuntius/email_tracking_service.rb +75 -0
- data/app/tables/nuntius/campaign_messages_table.rb +10 -1
- data/app/tables/nuntius/message_trackings_table.rb +21 -0
- data/app/tables/nuntius/messages_table.rb +3 -2
- data/app/views/layouts/empty.html.slim +21 -0
- data/app/views/nuntius/admin/campaigns/edit.html.slim +5 -1
- data/app/views/nuntius/admin/messages/show.html.slim +9 -0
- data/app/views/nuntius/admin/templates/edit.html.slim +4 -0
- data/config/locales/en.yml +5 -0
- data/config/locales/nl.yml +6 -0
- data/config/routes.rb +7 -1
- data/db/migrate/20250521132554_update_nuntius_events_index.rb +2 -2
- data/db/migrate/20260210122500_add_tracking_to_nuntius_messages.rb +24 -0
- data/lib/nuntius/active_storage_helpers.rb +1 -0
- data/lib/nuntius/devise.rb +2 -2
- data/lib/nuntius/version.rb +1 -1
- data/lib/nuntius.rb +1 -0
- metadata +12 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8ccc43faba64c7face2decf37a6c33b775ea9d6c13dd36cc08ec86da58da0bbd
|
|
4
|
+
data.tar.gz: f43c4cf84c238bd62956151a468a0adc62c1e798589e02b70ffe85be899a1ad8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: db7b5cd54668c544ad6ca9494b350ec2585e3f504bdeb68aa4ce2b8f56a344ee36d2d09b9802d8d3dbd8ecc9df5f8e2d59648ee30f577b65e64e14170a84e21a
|
|
7
|
+
data.tar.gz: 96dbae515949cf5c24d9936712ac8d0068283fce076314943eacb6439b98e0f36329a8e0a5a95f75e2b19322a4c9891117100d2dda90b9186f10e991391abb84
|
data/README.md
CHANGED
|
@@ -227,6 +227,9 @@ Outbound mail is handled through SMTP. We've only exposed the HTML of mail, we c
|
|
|
227
227
|
|
|
228
228
|
In the layout you can add `<a href="{{message_url}}">Link to mail</a>` to provide a link to the online version of the message.
|
|
229
229
|
|
|
230
|
+
Nuntius supports email tracking, to enable this, you need to enable tracking for a template.
|
|
231
|
+
Nuntius will then inject a tracking pixel and track clicks on links.
|
|
232
|
+
|
|
230
233
|
#### AWS SES
|
|
231
234
|
|
|
232
235
|
In case you use AWS SES, you can use the SNS Feedback Notifications to automatically mark messages as read, or deal with complaints and bounces. Create a AWS SNS topic, with a HTTPS subscription with the following URL (pattern):
|
|
@@ -30,7 +30,7 @@ module Nuntius
|
|
|
30
30
|
def update
|
|
31
31
|
@campaign.update(campaign_params)
|
|
32
32
|
|
|
33
|
-
respond_with :admin, @campaign
|
|
33
|
+
respond_with :admin, @campaign, action: :edit
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def publish
|
|
@@ -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)
|
|
54
|
+
params.require(:campaign).permit(:name, :transport, :layout_id, :list_id, :from, :subject, :text, :html, :metadata_yaml, :open_tracking, :link_tracking)
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
57
|
end
|
|
@@ -5,6 +5,8 @@ require_dependency "nuntius/application_admin_controller"
|
|
|
5
5
|
module Nuntius
|
|
6
6
|
module Admin
|
|
7
7
|
class ListsController < ApplicationAdminController
|
|
8
|
+
before_action :set_objects, except: [:index]
|
|
9
|
+
|
|
8
10
|
def index
|
|
9
11
|
@lists = Nuntius::List.visible.order(:name)
|
|
10
12
|
end
|
|
@@ -16,7 +18,7 @@ module Nuntius
|
|
|
16
18
|
|
|
17
19
|
def create
|
|
18
20
|
@list = Nuntius::List.create(list_params)
|
|
19
|
-
respond_with :admin, @list
|
|
21
|
+
respond_with :admin, @list, action: :edit
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def show
|
|
@@ -24,18 +26,18 @@ module Nuntius
|
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
def edit
|
|
27
|
-
@list = Nuntius::List.visible.find(params[:id])
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
def update
|
|
31
|
-
@list = Nuntius::List.visible.find(params[:id])
|
|
32
32
|
@list.update(list_params)
|
|
33
|
-
respond_with :admin, @list
|
|
33
|
+
respond_with :admin, @list, action: :edit
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
private
|
|
37
37
|
|
|
38
38
|
def set_objects
|
|
39
|
+
@list = Nuntius::List.visible.find(params[:id]) if params[:id]
|
|
40
|
+
@list ||= Nuntius::List.new
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
def list_params
|
|
@@ -49,7 +49,7 @@ module Nuntius
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def template_params
|
|
52
|
-
params.require(:template).permit(:enabled, :klass, :event, :interval, :transport, :description, :metadata_yaml, :payload, :from, :to, :subject, :layout_id, :html, :text, :payload)
|
|
52
|
+
params.require(:template).permit(:enabled, :klass, :event, :interval, :transport, :description, :metadata_yaml, :payload, :from, :to, :subject, :layout_id, :html, :text, :payload, :link_tracking, :open_tracking)
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
end
|
|
@@ -5,8 +5,55 @@ require_dependency "nuntius/application_controller"
|
|
|
5
5
|
module Nuntius
|
|
6
6
|
class MessagesController < ApplicationController
|
|
7
7
|
layout false
|
|
8
|
+
skip_before_action :verify_authenticity_token
|
|
9
|
+
before_action :set_objects
|
|
8
10
|
|
|
9
11
|
def show
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# GET /messages/:message_id/pixel.gif
|
|
15
|
+
def pixel
|
|
16
|
+
return if request.env["HTTP_REFERER"].to_s.include?(Nuntius.config.host(@message))
|
|
17
|
+
|
|
18
|
+
if @message&.open_tracking_enabled?
|
|
19
|
+
# Record the first open time
|
|
20
|
+
@message.opened_at ||= Time.current
|
|
21
|
+
@message.open_count = (@message.open_count || 0) + 1
|
|
22
|
+
@message.save
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Return a 1x1 transparent GIF pixel
|
|
26
|
+
send_data(Base64.decode64("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"), type: "image/gif", disposition: "inline")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# GET /messages/:message_id/link?url=...
|
|
30
|
+
def link
|
|
31
|
+
return if request.env["HTTP_REFERER"].to_s.include?(Nuntius.config.host(@message))
|
|
32
|
+
|
|
33
|
+
url = params[:url]
|
|
34
|
+
|
|
35
|
+
if @message&.link_tracking_enabled? && url.present?
|
|
36
|
+
# Record the first click time
|
|
37
|
+
@message.clicked_at ||= Time.current
|
|
38
|
+
@message.click_count = (@message.click_count || 0) + 1
|
|
39
|
+
@message.save
|
|
40
|
+
|
|
41
|
+
tracking = @message.message_trackings.find_or_create_by(url: url)
|
|
42
|
+
tracking.count = (tracking.count || 0) + 1
|
|
43
|
+
tracking.save!
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Redirect to the original URL
|
|
47
|
+
if url.present?
|
|
48
|
+
redirect_to url, allow_other_host: true
|
|
49
|
+
else
|
|
50
|
+
head :not_found
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def set_objects
|
|
10
57
|
@message = Nuntius::Message.find(params[:id])
|
|
11
58
|
end
|
|
12
59
|
end
|
|
@@ -5,6 +5,7 @@ require_dependency "nuntius/application_controller"
|
|
|
5
5
|
module Nuntius
|
|
6
6
|
class SubscribersController < ApplicationController
|
|
7
7
|
# skip_before_action :authenticate_user!, only: %i[show destroy]
|
|
8
|
+
layout "empty"
|
|
8
9
|
|
|
9
10
|
def show
|
|
10
11
|
@subscriber = Nuntius::Subscriber.find_by(id: params[:id])
|
|
@@ -19,14 +20,15 @@ module Nuntius
|
|
|
19
20
|
def subscribe
|
|
20
21
|
@subscriber = Nuntius::Subscriber.find(params[:id])
|
|
21
22
|
@subscriber.update(unsubscribed_at: nil)
|
|
22
|
-
|
|
23
|
+
Signum.success(request.session.id, text: "Subscription has been restored.")
|
|
24
|
+
|
|
23
25
|
redirect_to nuntius.subscriber_path(@subscriber), status: :see_other
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
def unsubscribe
|
|
27
29
|
@subscriber = Nuntius::Subscriber.find(params[:id])
|
|
28
30
|
@subscriber.touch(:unsubscribed_at)
|
|
29
|
-
|
|
31
|
+
Signum.success(request.session.id, text: "Subscription has been removed.")
|
|
30
32
|
redirect_to nuntius.subscriber_path(@subscriber), status: :see_other
|
|
31
33
|
end
|
|
32
34
|
end
|
|
@@ -19,6 +19,8 @@ module Nuntius
|
|
|
19
19
|
belongs_to :template, optional: true
|
|
20
20
|
belongs_to :parent_message, class_name: "Message", optional: true
|
|
21
21
|
has_many :child_messages, class_name: "Message", foreign_key: "parent_message_id", dependent: :destroy
|
|
22
|
+
has_many :message_trackings, class_name: "MessageTracking", dependent: :destroy
|
|
23
|
+
|
|
22
24
|
belongs_to :nuntiable, polymorphic: true, optional: true
|
|
23
25
|
|
|
24
26
|
validates :transport, presence: true
|
|
@@ -55,6 +57,14 @@ module Nuntius
|
|
|
55
57
|
status == "undelivered"
|
|
56
58
|
end
|
|
57
59
|
|
|
60
|
+
def opened?
|
|
61
|
+
opened_at.present?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def clicked?
|
|
65
|
+
clicked_at.present?
|
|
66
|
+
end
|
|
67
|
+
|
|
58
68
|
# Removes only pending child messages
|
|
59
69
|
def cleanup!
|
|
60
70
|
Nuntius::Message.where(status: "pending").where(parent_message: self).destroy_all
|
|
@@ -148,5 +158,13 @@ module Nuntius
|
|
|
148
158
|
klass.deliver(self)
|
|
149
159
|
self
|
|
150
160
|
end
|
|
161
|
+
|
|
162
|
+
def link_tracking_enabled?
|
|
163
|
+
template&.link_tracking? || campaign&.link_tracking?
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def open_tracking_enabled?
|
|
167
|
+
template&.open_tracking? || campaign&.open_tracking?
|
|
168
|
+
end
|
|
151
169
|
end
|
|
152
170
|
end
|
|
@@ -33,10 +33,10 @@ module Nuntius
|
|
|
33
33
|
|
|
34
34
|
options = {registers: {"template" => self, "message" => message}}
|
|
35
35
|
|
|
36
|
-
message.to = render(:to, assigns, locale, options).split(
|
|
36
|
+
message.to = render(:to, assigns, locale, options).split(",").map(&:strip).reject { |to| to == "Liquid error internal" }.reject(&:empty?).join(",")
|
|
37
37
|
return unless message.to.present?
|
|
38
38
|
|
|
39
|
-
message.from = render(:from, assigns, locale, options).split(
|
|
39
|
+
message.from = render(:from, assigns, locale, options).split(",").reject(&:empty?).join(",")
|
|
40
40
|
message.subject = render(:subject, assigns, locale, options)
|
|
41
41
|
message.html = render(:html, assigns, locale, options.merge(layout: layout&.data))
|
|
42
42
|
message.text = render(:text, assigns, locale, options)
|
|
@@ -45,7 +45,11 @@ module Nuntius
|
|
|
45
45
|
charset: "UTF-8"
|
|
46
46
|
)
|
|
47
47
|
if message.html.present?
|
|
48
|
+
# Apply email tracking (tracking pixel and link wrapping)
|
|
49
|
+
Nuntius::EmailTrackingService.perform(message: message)
|
|
50
|
+
|
|
48
51
|
message.html = message.html.gsub("{{message_url}}") { message_url(message) }
|
|
52
|
+
|
|
49
53
|
p.html_part = Mail::Part.new(
|
|
50
54
|
body: message.html,
|
|
51
55
|
content_type: "text/html",
|
|
@@ -70,7 +74,7 @@ module Nuntius
|
|
|
70
74
|
|
|
71
75
|
message.provider_id = mail.message_id
|
|
72
76
|
message.status = "undelivered"
|
|
73
|
-
message.status = "sent" if Rails.env.test?
|
|
77
|
+
message.status = "sent" if Rails.env.test? || response.success?
|
|
74
78
|
message.last_sent_at = Time.zone.now if message.sent?
|
|
75
79
|
|
|
76
80
|
message
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Use Nokogiri to parse and modify links
|
|
4
|
+
require "nokogiri"
|
|
5
|
+
|
|
6
|
+
module Nuntius
|
|
7
|
+
class EmailTrackingService < ApplicationService
|
|
8
|
+
context do
|
|
9
|
+
attribute :message
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def perform
|
|
13
|
+
return context.message.html unless context.message.link_tracking_enabled? || context.message.open_tracking_enabled?
|
|
14
|
+
|
|
15
|
+
tracked_html = context.message.html.dup
|
|
16
|
+
tracked_html = inject_tracking_pixel(tracked_html)
|
|
17
|
+
tracked_html = wrap_links_with_tracking(tracked_html)
|
|
18
|
+
|
|
19
|
+
context.message.html = tracked_html
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def inject_tracking_pixel(html)
|
|
25
|
+
return html unless context.message.open_tracking_enabled?
|
|
26
|
+
|
|
27
|
+
tracking_pixel_url = tracking_pixel_url(context.message.id)
|
|
28
|
+
tracking_pixel = %(<img src="#{tracking_pixel_url}" width="1" height="1" alt=""/>)
|
|
29
|
+
|
|
30
|
+
# Try to inject before closing body tag, otherwise append to end
|
|
31
|
+
if html.include?("</body>")
|
|
32
|
+
html.gsub("</body>", "#{tracking_pixel}</body>")
|
|
33
|
+
else
|
|
34
|
+
html + tracking_pixel
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def wrap_links_with_tracking(html)
|
|
39
|
+
return html unless context.message.link_tracking_enabled?
|
|
40
|
+
|
|
41
|
+
doc = Nokogiri::HTML.fragment(html)
|
|
42
|
+
|
|
43
|
+
doc.css("a[href]").each do |link|
|
|
44
|
+
original_url = link["href"]
|
|
45
|
+
|
|
46
|
+
# Skip if it's a mailto, tel, anchor link or liquid variable
|
|
47
|
+
next if original_url.start_with?("mailto:", "tel:", "#", "{{")
|
|
48
|
+
|
|
49
|
+
# Skip if it's already a tracking link
|
|
50
|
+
next if original_url.include?("/tracking/")
|
|
51
|
+
|
|
52
|
+
# Create tracking URL
|
|
53
|
+
tracking_url = tracking_link_url(context.message.id, url: original_url)
|
|
54
|
+
link["href"] = tracking_url
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
doc.to_html
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def tracking_pixel_url(message_id)
|
|
61
|
+
Nuntius::Engine.routes.url_helpers.tracking_pixel_message_url(
|
|
62
|
+
message_id,
|
|
63
|
+
host: Nuntius.config.host(context.message)
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def tracking_link_url(message_id, url:)
|
|
68
|
+
Nuntius::Engine.routes.url_helpers.tracking_link_message_url(
|
|
69
|
+
message_id,
|
|
70
|
+
url: url,
|
|
71
|
+
host: Nuntius.config.host(context.message)
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -8,6 +8,15 @@ module Nuntius
|
|
|
8
8
|
column(:to)
|
|
9
9
|
column(:status)
|
|
10
10
|
column(:created_at)
|
|
11
|
+
column(:last_sent_at)
|
|
12
|
+
action :resend do
|
|
13
|
+
link { |message| nuntius.resend_admin_message_path(message) }
|
|
14
|
+
icon "fal fa-rotate-right"
|
|
15
|
+
link_attributes data: {"turbo-confirm": "Are you sure you want to resend the message?", "turbo-method": :post}
|
|
16
|
+
show ->(message) { true }
|
|
17
|
+
end
|
|
18
|
+
column(:open_count)
|
|
19
|
+
column(:click_count)
|
|
11
20
|
|
|
12
21
|
order created_at: :desc
|
|
13
22
|
|
|
@@ -17,7 +26,7 @@ module Nuntius
|
|
|
17
26
|
private
|
|
18
27
|
|
|
19
28
|
def scope
|
|
20
|
-
@scope = Nuntius::Campaign.
|
|
29
|
+
@scope = Nuntius::Campaign.find(params[:campaign_id]).messages
|
|
21
30
|
@scope
|
|
22
31
|
end
|
|
23
32
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nuntius
|
|
4
|
+
class MessageTrackingsTable < Nuntius::ApplicationTable
|
|
5
|
+
definition do
|
|
6
|
+
model Nuntius::MessageTracking
|
|
7
|
+
|
|
8
|
+
column(:url)
|
|
9
|
+
column(:count)
|
|
10
|
+
column(:updated_at)
|
|
11
|
+
|
|
12
|
+
order updated_at: :desc
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def scope
|
|
18
|
+
@scope = Nuntius::MessageTracking.where(message_id: params[:message_id])
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
module Nuntius
|
|
4
4
|
class MessagesTable < Nuntius::ApplicationTable
|
|
5
|
-
|
|
6
5
|
definition do
|
|
7
6
|
model Nuntius::Message
|
|
8
7
|
|
|
@@ -15,7 +14,9 @@ module Nuntius
|
|
|
15
14
|
link_attributes data: {"turbo-confirm": "Are you sure you want to resend the message?", "turbo-method": :post}
|
|
16
15
|
show ->(message) { true }
|
|
17
16
|
end
|
|
18
|
-
|
|
17
|
+
column(:open_count)
|
|
18
|
+
column(:click_count)
|
|
19
|
+
|
|
19
20
|
column(:campaign_id) do
|
|
20
21
|
render do
|
|
21
22
|
html do |message|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
head.h-full.bg-white
|
|
2
|
+
title= t(:app_name)
|
|
3
|
+
meta name="viewport" content="width=device-width, initial-scale=1.0"
|
|
4
|
+
meta name="turbo-prefetch" content="false"
|
|
5
|
+
= javascript_include_tag asset_url("fontawesome.js"), defer: true
|
|
6
|
+
= javascript_include_tag asset_url("solid.js"), defer: true
|
|
7
|
+
= javascript_include_tag asset_url("duotone.js"), defer: true
|
|
8
|
+
= javascript_include_tag asset_url("light.js"), defer: true
|
|
9
|
+
|
|
10
|
+
= javascript_importmap_tags "application"
|
|
11
|
+
= stylesheet_link_tag "tailwind", "data-turbo-track": "reload"
|
|
12
|
+
= csrf_meta_tags
|
|
13
|
+
= csp_meta_tag
|
|
14
|
+
- if content_for?(:head)
|
|
15
|
+
= yield(:head)
|
|
16
|
+
|
|
17
|
+
body
|
|
18
|
+
main
|
|
19
|
+
= yield
|
|
20
|
+
|
|
21
|
+
= render(Signum::Notifications::Component.new([request.session.id]))
|
|
@@ -18,12 +18,16 @@
|
|
|
18
18
|
= f.association :list, collection: @lists
|
|
19
19
|
|
|
20
20
|
.col-span-12
|
|
21
|
-
= f.input :from, hint:
|
|
21
|
+
= f.input :from, hint: t(".from_hint")
|
|
22
22
|
|
|
23
23
|
.col-span-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-6
|
|
28
|
+
= f.input :link_tracking, as: :switch
|
|
29
|
+
.col-span-6
|
|
30
|
+
= f.input :open_tracking, as: :switch
|
|
27
31
|
.col-span-12
|
|
28
32
|
= f.input :subject
|
|
29
33
|
.col-span-12
|
|
@@ -34,3 +34,12 @@
|
|
|
34
34
|
= info.with_item :refreshes, content: @message.refreshes, class: "sm:col-span-1"
|
|
35
35
|
= info.with_item :last_sent_at, content: ln(@message.last_sent_at), class: "sm:col-span-1", icon: 'fal fa-envelope'
|
|
36
36
|
= info.with_item :created_at, content: ln(@message.created_at), class: "sm:col-span-1", icon: 'fal fa-calendar'
|
|
37
|
+
- if @message.link_tracking_enabled?
|
|
38
|
+
= info.with_item :click_count, content: @message.click_count, class: "sm:col-span-1"
|
|
39
|
+
- if @message.open_tracking_enabled?
|
|
40
|
+
= info.with_item :open_count, content: @message.open_count, class: "sm:col-span-1"
|
|
41
|
+
|
|
42
|
+
- if @message.link_tracking_enabled? || @message.open_tracking_enabled?
|
|
43
|
+
- card.with_tab:tracking
|
|
44
|
+
= sts.table :"nuntius/message_trackings", params: {message_id: @message.id}
|
|
45
|
+
|
|
@@ -50,6 +50,10 @@
|
|
|
50
50
|
= f.input :metadata_yaml, as: :editor, mode: 'application/yaml', label: t('.metadata')
|
|
51
51
|
|
|
52
52
|
template data-toggle-target='toggleable' data-toggle-value='mail'
|
|
53
|
+
.col-span-6
|
|
54
|
+
= f.input :link_tracking, as: :switch
|
|
55
|
+
.col-span-6
|
|
56
|
+
= f.input :open_tracking, as: :switch
|
|
53
57
|
.col-span-12
|
|
54
58
|
= f.input :subject, as: :editor, mode: 'text/plain'
|
|
55
59
|
.col-span-12
|
data/config/locales/en.yml
CHANGED
|
@@ -10,6 +10,7 @@ en:
|
|
|
10
10
|
state: Staat
|
|
11
11
|
subject: Subject
|
|
12
12
|
transport: Transport
|
|
13
|
+
html: Body
|
|
13
14
|
nuntius/layout:
|
|
14
15
|
name: Name
|
|
15
16
|
nuntius/list:
|
|
@@ -55,6 +56,7 @@ en:
|
|
|
55
56
|
card:
|
|
56
57
|
nuntius_admin_campaigns:
|
|
57
58
|
title: Campaigns
|
|
59
|
+
from_hint: "You can leave this blank, only fill this in if you want to override the default"
|
|
58
60
|
metadata: Metadata
|
|
59
61
|
index:
|
|
60
62
|
card:
|
|
@@ -138,12 +140,15 @@ en:
|
|
|
138
140
|
subject: Subject
|
|
139
141
|
to: To
|
|
140
142
|
transport: Transport
|
|
143
|
+
click_count: Click Count
|
|
144
|
+
open_count: Open Count
|
|
141
145
|
message: Message
|
|
142
146
|
tabs:
|
|
143
147
|
main:
|
|
144
148
|
tab:
|
|
145
149
|
details: Details
|
|
146
150
|
preview: Preview
|
|
151
|
+
tracking: Tracking
|
|
147
152
|
templates:
|
|
148
153
|
edit:
|
|
149
154
|
card:
|
data/config/locales/nl.yml
CHANGED
|
@@ -10,6 +10,7 @@ nl:
|
|
|
10
10
|
state: Staat
|
|
11
11
|
subject: Onderwerp
|
|
12
12
|
transport: Transport
|
|
13
|
+
html: Body
|
|
13
14
|
nuntius/layout:
|
|
14
15
|
name: Naam
|
|
15
16
|
nuntius/list:
|
|
@@ -56,6 +57,7 @@ nl:
|
|
|
56
57
|
nuntius_admin_campaigns:
|
|
57
58
|
title: Campagne
|
|
58
59
|
metadata: Metadata
|
|
60
|
+
from_hint: "Je kunt dit leeg laten, vul dit alleen in als je de standaard wilt overschrijven"
|
|
59
61
|
index:
|
|
60
62
|
card:
|
|
61
63
|
nuntius_admin_campaigns:
|
|
@@ -140,12 +142,16 @@ nl:
|
|
|
140
142
|
subject: Onderwerp
|
|
141
143
|
to: Aan
|
|
142
144
|
transport: Transport
|
|
145
|
+
click_count: Click Count
|
|
146
|
+
open_count: Open Count
|
|
147
|
+
|
|
143
148
|
message: Message
|
|
144
149
|
tabs:
|
|
145
150
|
main:
|
|
146
151
|
tab:
|
|
147
152
|
details: Details
|
|
148
153
|
preview: Preview
|
|
154
|
+
tracking: Tracking
|
|
149
155
|
templates:
|
|
150
156
|
edit:
|
|
151
157
|
card:
|
data/config/routes.rb
CHANGED
|
@@ -10,7 +10,13 @@ Nuntius::Engine.routes.draw do
|
|
|
10
10
|
|
|
11
11
|
post "feedback/awssns" => "feedback#awssns"
|
|
12
12
|
|
|
13
|
-
resources :messages
|
|
13
|
+
resources :messages do
|
|
14
|
+
# Email tracking routes
|
|
15
|
+
member do
|
|
16
|
+
get "pixel", to: "messages#pixel", as: :tracking_pixel
|
|
17
|
+
get "link", to: "messages#link", as: :tracking_link
|
|
18
|
+
end
|
|
19
|
+
end
|
|
14
20
|
resources :campaigns
|
|
15
21
|
resources :subscribers do
|
|
16
22
|
member do
|
|
@@ -3,7 +3,7 @@ class UpdateNuntiusEventsIndex < ActiveRecord::Migration[7.0]
|
|
|
3
3
|
remove_index :nuntius_events, name: :index_nuntius_events_on_transitionable
|
|
4
4
|
|
|
5
5
|
add_index :nuntius_events,
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
[:transitionable_type, :transitionable_id, :transition_event],
|
|
7
|
+
name: :index_nuntius_events_on_type_id_event
|
|
8
8
|
end
|
|
9
9
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddTrackingToNuntiusMessages < ActiveRecord::Migration[7.0]
|
|
4
|
+
def change
|
|
5
|
+
add_column :nuntius_messages, :opened_at, :datetime
|
|
6
|
+
add_column :nuntius_messages, :clicked_at, :datetime
|
|
7
|
+
add_column :nuntius_messages, :open_count, :integer, default: 0
|
|
8
|
+
add_column :nuntius_messages, :click_count, :integer, default: 0
|
|
9
|
+
|
|
10
|
+
add_column :nuntius_templates, :open_tracking, :boolean, default: false
|
|
11
|
+
add_column :nuntius_templates, :link_tracking, :boolean, default: false
|
|
12
|
+
|
|
13
|
+
create_table :nuntius_message_trackings, id: :uuid do |t|
|
|
14
|
+
t.references :message, type: :uuid, foreign_key: {to_table: :nuntius_messages}
|
|
15
|
+
t.string :url
|
|
16
|
+
t.integer :count, default: 0
|
|
17
|
+
|
|
18
|
+
t.timestamps
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
add_column :nuntius_campaigns, :open_tracking, :boolean, default: false
|
|
22
|
+
add_column :nuntius_campaigns, :link_tracking, :boolean, default: false
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/nuntius/devise.rb
CHANGED
|
@@ -7,13 +7,13 @@ module Nuntius
|
|
|
7
7
|
included do
|
|
8
8
|
raise "#{name} must be nuntiable" unless nuntiable?
|
|
9
9
|
|
|
10
|
-
I18n.t(
|
|
10
|
+
I18n.t("devise.mailer").keys.map(&:to_s).each do |event_name|
|
|
11
11
|
messenger.send(:define_method, event_name) { |object, options = {}| }
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
define_method(:send_devise_notification) do |notification, *devise_params|
|
|
15
15
|
# All notifications have either a token as the first param, or nothing
|
|
16
|
-
Nuntius.event(notification, self, {
|
|
16
|
+
Nuntius.event(notification, self, {token: devise_params.first})
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
end
|
data/lib/nuntius/version.rb
CHANGED
data/lib/nuntius.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.
|
|
4
|
+
version: 1.4.9
|
|
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-
|
|
11
|
+
date: 2026-02-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: apnotic
|
|
@@ -416,7 +416,9 @@ dependencies:
|
|
|
416
416
|
- - "~>"
|
|
417
417
|
- !ruby/object:Gem::Version
|
|
418
418
|
version: '1'
|
|
419
|
-
description:
|
|
419
|
+
description: |
|
|
420
|
+
Messsages can be sent based on creating, updating, or destroying a record.
|
|
421
|
+
It also uses state-events to send messages based on state changes.
|
|
420
422
|
email:
|
|
421
423
|
- tom@degrunt.nl
|
|
422
424
|
executables: []
|
|
@@ -476,6 +478,7 @@ files:
|
|
|
476
478
|
- app/models/nuntius/list.rb
|
|
477
479
|
- app/models/nuntius/locale.rb
|
|
478
480
|
- app/models/nuntius/message.rb
|
|
481
|
+
- app/models/nuntius/message_tracking.rb
|
|
479
482
|
- app/models/nuntius/subscriber.rb
|
|
480
483
|
- app/models/nuntius/template.rb
|
|
481
484
|
- app/presenters/application_presenter.rb
|
|
@@ -494,6 +497,7 @@ files:
|
|
|
494
497
|
- app/services/nuntius/aws_sns_processor_service.rb
|
|
495
498
|
- app/services/nuntius/deliver_campaign_service.rb
|
|
496
499
|
- app/services/nuntius/deliver_inbound_message_service.rb
|
|
500
|
+
- app/services/nuntius/email_tracking_service.rb
|
|
497
501
|
- app/services/nuntius/retrieve_inbound_mail_service.rb
|
|
498
502
|
- app/tables/nuntius/application_table.rb
|
|
499
503
|
- app/tables/nuntius/campaign_messages_table.rb
|
|
@@ -501,6 +505,7 @@ files:
|
|
|
501
505
|
- app/tables/nuntius/layouts_table.rb
|
|
502
506
|
- app/tables/nuntius/lists_table.rb
|
|
503
507
|
- app/tables/nuntius/locales_table.rb
|
|
508
|
+
- app/tables/nuntius/message_trackings_table.rb
|
|
504
509
|
- app/tables/nuntius/messages_table.rb
|
|
505
510
|
- app/tables/nuntius/subscribers_table.rb
|
|
506
511
|
- app/tables/nuntius/templates_table.rb
|
|
@@ -512,6 +517,7 @@ files:
|
|
|
512
517
|
- app/transports/nuntius/teams_transport.rb
|
|
513
518
|
- app/transports/nuntius/voice_transport.rb
|
|
514
519
|
- app/validators/liquid_validator.rb
|
|
520
|
+
- app/views/layouts/empty.html.slim
|
|
515
521
|
- app/views/nuntius/admin/campaigns/edit.html.slim
|
|
516
522
|
- app/views/nuntius/admin/campaigns/index.html.slim
|
|
517
523
|
- app/views/nuntius/admin/layouts/edit.html.slim
|
|
@@ -559,6 +565,7 @@ files:
|
|
|
559
565
|
- db/migrate/20250520091916_create_nuntius_events_table.rb
|
|
560
566
|
- db/migrate/20250521132554_update_nuntius_events_index.rb
|
|
561
567
|
- db/migrate/20260123160443_change_nuntius_events_id.rb
|
|
568
|
+
- db/migrate/20260210122500_add_tracking_to_nuntius_messages.rb
|
|
562
569
|
- lib/generators/nuntius/install_generator.rb
|
|
563
570
|
- lib/generators/nuntius/tailwind_config_generator.rb
|
|
564
571
|
- lib/generators/nuntius/templates/config/initializers/nuntius.rb
|
|
@@ -599,5 +606,6 @@ requirements: []
|
|
|
599
606
|
rubygems_version: 3.4.10
|
|
600
607
|
signing_key:
|
|
601
608
|
specification_version: 4
|
|
602
|
-
summary:
|
|
609
|
+
summary: Easily send emails, sms messages, teams & slack messages, and push notifications
|
|
610
|
+
from your Rails app.
|
|
603
611
|
test_files: []
|