rails_mail 0.9.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +170 -0
- data/Rakefile +8 -0
- data/app/assets/images/rails_mail/rails-mail.png +0 -0
- data/app/channels/rails_mail/application_cable/channel.rb +6 -0
- data/app/channels/rails_mail/application_cable/connection.rb +17 -0
- data/app/channels/rails_mail/emails_channel.rb +7 -0
- data/app/controllers/rails_mail/base_controller.rb +13 -0
- data/app/controllers/rails_mail/emails_controller.rb +40 -0
- data/app/controllers/rails_mail/frontends_controller.rb +51 -0
- data/app/frontend/rails_mail/application.js +15 -0
- data/app/frontend/rails_mail/modules/auto_submit.js +30 -0
- data/app/frontend/rails_mail/modules/consumer.js +3 -0
- data/app/frontend/rails_mail/modules/email_highlight_controller.js +48 -0
- data/app/frontend/rails_mail/modules/emails_channel.js +12 -0
- data/app/frontend/rails_mail/modules/timeago.js +41 -0
- data/app/frontend/rails_mail/style.css +0 -0
- data/app/frontend/rails_mail/vendor/action_cable.js +512 -0
- data/app/frontend/rails_mail/vendor/date-fns.js +8 -0
- data/app/frontend/rails_mail/vendor/stimulus.js +2408 -0
- data/app/frontend/rails_mail/vendor/tailwind/tailwind.min.js +83 -0
- data/app/frontend/rails_mail/vendor/turbo.js +6697 -0
- data/app/helpers/rails_mail/emails_helper.rb +4 -0
- data/app/helpers/rails_mail/turbo_helper.rb +41 -0
- data/app/jobs/rails_mail/trim_emails_job.rb +40 -0
- data/app/models/rails_mail/email.rb +45 -0
- data/app/views/layouts/rails_mail/application.html.erb +72 -0
- data/app/views/rails_mail/emails/_email.html.erb +7 -0
- data/app/views/rails_mail/emails/_empty_state.html.erb +5 -0
- data/app/views/rails_mail/emails/_form.html.erb +22 -0
- data/app/views/rails_mail/emails/_show.html.erb +50 -0
- data/app/views/rails_mail/emails/edit.html.erb +12 -0
- data/app/views/rails_mail/emails/index.html.erb +16 -0
- data/app/views/rails_mail/emails/index.turbo_stream.erb +7 -0
- data/app/views/rails_mail/emails/new.html.erb +11 -0
- data/app/views/rails_mail/emails/show.html.erb +43 -0
- data/app/views/rails_mail/shared/_email.html.erb +25 -0
- data/app/views/rails_mail/shared/_email_sidebar.html.erb +9 -0
- data/config/routes.rb +13 -0
- data/db/migrate/20250118201227_create_rails_mail_emails.rb +9 -0
- data/lib/generators/rails_mail/install/install_generator.rb +15 -0
- data/lib/generators/rails_mail/install/templates/initializer.rb +23 -0
- data/lib/rails_mail/configuration.rb +35 -0
- data/lib/rails_mail/delivery_method.rb +22 -0
- data/lib/rails_mail/engine.rb +11 -0
- data/lib/rails_mail/version.rb +3 -0
- data/lib/rails_mail.rb +40 -0
- data/lib/tasks/rails_mail_tasks.rake +10 -0
- metadata +132 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
module RailsMail
|
2
|
+
module TurboHelper
|
3
|
+
def turbo_frame_tag(name, src: nil, &block)
|
4
|
+
tag.turbo_frame id: name, src: src do
|
5
|
+
if block_given?
|
6
|
+
yield
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def turbo_stream_from(*streamables, **attributes)
|
12
|
+
return unless defined?(::ActionCable)
|
13
|
+
|
14
|
+
raise ArgumentError, "streamables can't be blank" unless streamables.any?(&:present?)
|
15
|
+
attributes[:channel] = attributes[:channel]&.to_s || "Turbo::StreamsChannel"
|
16
|
+
# attributes[:"signed-stream-name"] = Turbo::StreamsChannel.signed_stream_name(streamables)
|
17
|
+
|
18
|
+
tag.turbo_cable_stream_source(**attributes)
|
19
|
+
end
|
20
|
+
|
21
|
+
def turbo_stream
|
22
|
+
TurboStreamBuilder.new
|
23
|
+
end
|
24
|
+
|
25
|
+
class TurboStreamBuilder
|
26
|
+
def append(target:, content:)
|
27
|
+
build_stream("append", target, content)
|
28
|
+
end
|
29
|
+
|
30
|
+
def prepend(target:, content:)
|
31
|
+
build_stream("prepend", target, content)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def build_stream(action, target, content)
|
37
|
+
%(<turbo-stream action="#{action}" target="#{target}"><template>#{content}</template></turbo-stream>)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module RailsMail
|
2
|
+
class TrimEmailsJob < ApplicationJob
|
3
|
+
queue_as :default
|
4
|
+
|
5
|
+
def perform
|
6
|
+
return unless should_trim?
|
7
|
+
|
8
|
+
if RailsMail.configuration.trim_emails_older_than
|
9
|
+
trim_by_age
|
10
|
+
end
|
11
|
+
|
12
|
+
if RailsMail.configuration.trim_emails_max_count
|
13
|
+
trim_by_count
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def should_trim?
|
20
|
+
RailsMail.configuration.trim_emails_older_than.present? ||
|
21
|
+
RailsMail.configuration.trim_emails_max_count.present?
|
22
|
+
end
|
23
|
+
|
24
|
+
def trim_by_age
|
25
|
+
cutoff_date = RailsMail.configuration.trim_emails_older_than.ago
|
26
|
+
RailsMail::Email.where("created_at < ?", cutoff_date).destroy_all
|
27
|
+
end
|
28
|
+
|
29
|
+
def trim_by_count
|
30
|
+
max_count = RailsMail.configuration.trim_emails_max_count
|
31
|
+
return unless max_count.positive?
|
32
|
+
|
33
|
+
emails_to_delete = RailsMail::Email
|
34
|
+
.order(created_at: :desc)
|
35
|
+
.offset(max_count)
|
36
|
+
|
37
|
+
emails_to_delete.destroy_all
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module RailsMail
|
2
|
+
class Email < ApplicationRecord
|
3
|
+
store_accessor :data, :from, :to, :cc, :bcc, :subject, :body, :content_type, :attachments
|
4
|
+
|
5
|
+
validates :from, presence: true
|
6
|
+
validates :to, presence: true
|
7
|
+
|
8
|
+
after_create_commit :broadcast_email
|
9
|
+
after_create :schedule_trim_job
|
10
|
+
|
11
|
+
scope :search, ->(query) {
|
12
|
+
where("CAST(data AS CHAR) LIKE :q", q: "%#{query}%")
|
13
|
+
}
|
14
|
+
|
15
|
+
def text?
|
16
|
+
content_type&.include?("text/plain")
|
17
|
+
end
|
18
|
+
|
19
|
+
def html?
|
20
|
+
content_type&.include?("text/html")
|
21
|
+
end
|
22
|
+
private
|
23
|
+
|
24
|
+
def broadcast_email
|
25
|
+
return unless defined?(::Turbo) && defined?(::ActionCable)
|
26
|
+
|
27
|
+
::Turbo::StreamsChannel.broadcast_prepend_to(
|
28
|
+
"rails_mail:emails",
|
29
|
+
target: "email-sidebar",
|
30
|
+
partial: "rails_mail/shared/email",
|
31
|
+
locals: { email: self }
|
32
|
+
)
|
33
|
+
rescue NameError => e
|
34
|
+
Rails.logger.debug "Skipping broadcast: #{e.message}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def schedule_trim_job
|
38
|
+
if RailsMail.configuration.sync_via == :now
|
39
|
+
RailsMail::TrimEmailsJob.perform_now
|
40
|
+
else
|
41
|
+
RailsMail::TrimEmailsJob.perform_later
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>RailsMail - <%= yield(:title) if content_for?(:title) %></title>
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
6
|
+
<%= action_cable_meta_tag if defined?(::ActionCable) %>
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
<%= csp_meta_tag %>
|
9
|
+
<%= tag.link rel: "stylesheet", href: frontend_static_path(:style, format: :css, v: RailsMail::VERSION, locale: nil), nonce: content_security_policy_nonce %>
|
10
|
+
|
11
|
+
<%= tag.script "", src: frontend_static_path(:tailwind, format: :js, v: RailsMail::VERSION, locale: nil), nonce: content_security_policy_nonce %>
|
12
|
+
|
13
|
+
<% importmaps = RailsMail::FrontendsController.js_modules.keys.index_with { |module_name| frontend_module_path(module_name, format: :js, locale: nil) } %>
|
14
|
+
<%= tag.script({ imports: importmaps }.to_json.html_safe, type: "importmap", nonce: content_security_policy_nonce) %>
|
15
|
+
<%= tag.script "", type: "module", nonce: content_security_policy_nonce do %> import "application"; <% end %>
|
16
|
+
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body class="bg-gray-100">
|
20
|
+
<div class="flex h-screen">
|
21
|
+
<!-- Sidebar -->
|
22
|
+
<div class="w-80 bg-white border-r border-gray-200 flex flex-col">
|
23
|
+
<div class="flex-none p-4">
|
24
|
+
<div class="flex items-center justify-between">
|
25
|
+
<div class="flex items-center">
|
26
|
+
<%= link_to root_path, class: "text-gray-900" do %>
|
27
|
+
<h1 class="text-xl font-bold">Rails Mail</h1>
|
28
|
+
<% end %>
|
29
|
+
</div>
|
30
|
+
|
31
|
+
<% if instance_eval(&RailsMail.show_clear_button?) %>
|
32
|
+
<%= button_to destroy_all_emails_path,
|
33
|
+
class: "text-sm text-gray-600 hover:text-gray-900",
|
34
|
+
form: { data: { turbo_confirm: "Are you sure you want to clear all emails?" } },
|
35
|
+
method: :delete do %>
|
36
|
+
<div class="flex items-center">
|
37
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
38
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
39
|
+
</svg>
|
40
|
+
Clear All
|
41
|
+
</div>
|
42
|
+
<% end %>
|
43
|
+
<% end %>
|
44
|
+
</div>
|
45
|
+
</div>
|
46
|
+
|
47
|
+
<div id="search-form" class="flex-none px-2 pb-2">
|
48
|
+
<%= form_with url: rails_mail.emails_path, method: :get, data: { turbo_stream: true, turbo_frame: "emails_list",controller: 'auto-submit' } do |f| %>
|
49
|
+
<%= f.search_field :q,
|
50
|
+
value: params[:q],
|
51
|
+
class: "w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500",
|
52
|
+
placeholder: "Search emails...",
|
53
|
+
data: { action: 'keyup->auto-submit#submit search->auto-submit#submit' } %>
|
54
|
+
<% end %>
|
55
|
+
</div>
|
56
|
+
|
57
|
+
<nav class="flex-1 overflow-y-auto px-2">
|
58
|
+
<%= render "rails_mail/shared/email_sidebar" %>
|
59
|
+
</nav>
|
60
|
+
</div>
|
61
|
+
|
62
|
+
<!-- Main content -->
|
63
|
+
<div class="flex-1 overflow-y-auto">
|
64
|
+
<div class="p-8">
|
65
|
+
<turbo-frame id="email_content">
|
66
|
+
<%= yield %>
|
67
|
+
</turbo-frame>
|
68
|
+
</div>
|
69
|
+
</div>
|
70
|
+
</div>
|
71
|
+
</body>
|
72
|
+
</html>
|
@@ -0,0 +1,5 @@
|
|
1
|
+
<div class="flex flex-col items-center justify-center h-screen">
|
2
|
+
<h1 class="text-2xl font-bold mb-4">No emails at the moment</h1>
|
3
|
+
<h2 class="text-xl mb-16">Once emails are sent from your app, they will appear here</h2>
|
4
|
+
<%= image_tag "rails_mail/rails-mail.png", class: "mb-16" %>
|
5
|
+
</div>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<%= form_with(model: email) do |form| %>
|
2
|
+
<% if email.errors.any? %>
|
3
|
+
<div style="color: red">
|
4
|
+
<h2><%= pluralize(email.errors.count, "error") %> prohibited this email from being saved:</h2>
|
5
|
+
|
6
|
+
<ul>
|
7
|
+
<% email.errors.each do |error| %>
|
8
|
+
<li><%= error.full_message %></li>
|
9
|
+
<% end %>
|
10
|
+
</ul>
|
11
|
+
</div>
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
<div>
|
15
|
+
<%= form.label :data, style: "display: block" %>
|
16
|
+
<%= form.text_field :data %>
|
17
|
+
</div>
|
18
|
+
|
19
|
+
<div>
|
20
|
+
<%= form.submit %>
|
21
|
+
</div>
|
22
|
+
<% end %>
|
@@ -0,0 +1,50 @@
|
|
1
|
+
<%= turbo_frame_tag "email_content" do %>
|
2
|
+
<div class="bg-white shadow rounded-lg" data-email-id="<%= email.id %>"
|
3
|
+
data-email-highlight-current-id-value="<%= email.id %>">
|
4
|
+
<div class="px-6 py-4 border-b border-gray-200">
|
5
|
+
<h1 class="text-2xl font-semibold text-gray-900"><%= email.subject %></h1>
|
6
|
+
<div class="mt-2 text-sm text-gray-600">
|
7
|
+
<div>From: <%= email.from %></div>
|
8
|
+
<div>To: <%= email.to.join(", ") %></div>
|
9
|
+
<% if email.cc.present? %>
|
10
|
+
<div>CC: <%= email.cc.join(", ") %></div>
|
11
|
+
<% end %>
|
12
|
+
<% if email.bcc.present? %>
|
13
|
+
<div>BCC: <%= email.bcc.join(", ") %></div>
|
14
|
+
<% end %>
|
15
|
+
<div class="text-gray-400 mt-1">
|
16
|
+
<%= email.created_at.strftime("%B %d, %Y at %I:%M %p") %>
|
17
|
+
</div>
|
18
|
+
</div>
|
19
|
+
</div>
|
20
|
+
|
21
|
+
<div class="px-6 py-4">
|
22
|
+
<% if email.attachments.present? %>
|
23
|
+
<div class="mb-4 p-4 bg-gray-50 rounded-md">
|
24
|
+
<h3 class="text-sm font-medium text-gray-900 mb-2">Attachments</h3>
|
25
|
+
<div class="space-y-2">
|
26
|
+
<% email.attachments.each do |attachment| %>
|
27
|
+
<div class="flex items-center text-sm">
|
28
|
+
<svg class="h-5 w-5 text-gray-400 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
29
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" />
|
30
|
+
</svg>
|
31
|
+
<span><%= attachment["filename"] %></span>
|
32
|
+
<span class="ml-2 text-gray-500">(<%= attachment["content_type"] %>)</span>
|
33
|
+
</div>
|
34
|
+
<% end %>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
<% end %>
|
38
|
+
|
39
|
+
<div class="prose max-w-none">
|
40
|
+
<% if email.html? %>
|
41
|
+
<%= sanitize(email.body,
|
42
|
+
tags: ActionView::Base.sanitized_allowed_tags + ['table', 'tbody', 'tr', 'td'],
|
43
|
+
attributes: ActionView::Base.sanitized_allowed_attributes + ['style']) %>
|
44
|
+
<% else %>
|
45
|
+
<%= simple_format email.body %>
|
46
|
+
<% end %>
|
47
|
+
</div>
|
48
|
+
</div>
|
49
|
+
</div>
|
50
|
+
<% end %>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<% if @email.present? %>
|
2
|
+
<%= turbo_frame_tag "email_content", src: email_path(@email) do %>
|
3
|
+
<div class="bg-white shadow rounded-lg">
|
4
|
+
<div class="px-6 py-4">
|
5
|
+
<div class="animate-pulse">
|
6
|
+
<div class="h-4 bg-gray-200 rounded w-3/4 mb-4"></div>
|
7
|
+
<div class="h-3 bg-gray-200 rounded w-1/2 mb-2"></div>
|
8
|
+
<div class="h-3 bg-gray-200 rounded w-1/3"></div>
|
9
|
+
</div>
|
10
|
+
</div>
|
11
|
+
</div>
|
12
|
+
<% end %>
|
13
|
+
<% else %>
|
14
|
+
<%= render "rails_mail/emails/empty_state" %>
|
15
|
+
<% end %>
|
16
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
<%= turbo_frame_tag "email_content" do %>
|
2
|
+
<div class="bg-white shadow rounded-lg">
|
3
|
+
<div class="px-6 py-4 border-b border-gray-200">
|
4
|
+
<h1 class="text-2xl font-semibold text-gray-900"><%= @email.subject %></h1>
|
5
|
+
<div class="mt-2 text-sm text-gray-600">
|
6
|
+
<div>From: <%= @email.from %></div>
|
7
|
+
<div>To: <%= @email.to.join(", ") %></div>
|
8
|
+
<% if @email.cc.present? %>
|
9
|
+
<div>CC: <%= @email.cc.join(", ") %></div>
|
10
|
+
<% end %>
|
11
|
+
<% if @email.bcc.present? %>
|
12
|
+
<div>BCC: <%= @email.bcc.join(", ") %></div>
|
13
|
+
<% end %>
|
14
|
+
<div class="text-gray-400 mt-1">
|
15
|
+
<%= @email.created_at.strftime("%B %d, %Y at %I:%M %p") %>
|
16
|
+
</div>
|
17
|
+
</div>
|
18
|
+
</div>
|
19
|
+
|
20
|
+
<div class="px-6 py-4">
|
21
|
+
<% if @email.attachments.present? %>
|
22
|
+
<div class="mb-4 p-4 bg-gray-50 rounded-md">
|
23
|
+
<h3 class="text-sm font-medium text-gray-900 mb-2">Attachments</h3>
|
24
|
+
<div class="space-y-2">
|
25
|
+
<% @email.attachments.each do |attachment| %>
|
26
|
+
<div class="flex items-center text-sm">
|
27
|
+
<svg class="h-5 w-5 text-gray-400 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
28
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" />
|
29
|
+
</svg>
|
30
|
+
<span><%= attachment["filename"] %></span>
|
31
|
+
<span class="ml-2 text-gray-500">(<%= attachment["content_type"] %>)</span>
|
32
|
+
</div>
|
33
|
+
<% end %>
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
<% end %>
|
37
|
+
|
38
|
+
<div class="prose max-w-none">
|
39
|
+
<%= simple_format @email.body %>
|
40
|
+
</div>
|
41
|
+
</div>
|
42
|
+
</div>
|
43
|
+
<% end %>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<%
|
2
|
+
on_current_page = current_page?(RailsMail::Engine.routes.url_helpers.email_path(email))
|
3
|
+
highlight_class = on_current_page ? 'bg-gray-100' : ''
|
4
|
+
%>
|
5
|
+
<%= link_to RailsMail::Engine.routes.url_helpers.email_path(email),
|
6
|
+
class: "block px-3 py-2 rounded-md hover:bg-gray-50 #{highlight_class} active:bg-gray-100 focus:bg-gray-100",
|
7
|
+
data: {
|
8
|
+
"turbo-frame": "email_content",
|
9
|
+
"turbo-action": "advance",
|
10
|
+
"turbo-prefetch": "false",
|
11
|
+
"email-highlight-target": "link",
|
12
|
+
"email-id": email.id,
|
13
|
+
action: "click->email-highlight#highlight"
|
14
|
+
} do %>
|
15
|
+
<div class="text-sm font-medium text-gray-900 truncate"><%= email.subject %></div>
|
16
|
+
<div class="text-xs text-gray-500">
|
17
|
+
<span class="truncate"><%= email.from %></span>
|
18
|
+
<time
|
19
|
+
data-controller="timeago"
|
20
|
+
data-timeago-datetime-value="<%= email.created_at.iso8601 %>"
|
21
|
+
data-timeago-refresh-interval-value="1000"
|
22
|
+
data-timeago-include-seconds-value="true"
|
23
|
+
class="ml-2 text-gray-400"><%= time_ago_in_words(email.created_at) %> ago</time>
|
24
|
+
</div>
|
25
|
+
<% end %>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<% if defined?(::ActionCable) %>
|
2
|
+
<%= turbo_stream_from "rails_mail:emails", channel: "RailsMail::EmailsChannel" %>
|
3
|
+
<% end %>
|
4
|
+
|
5
|
+
<div id="email-sidebar" data-controller="email-highlight">
|
6
|
+
<% @emails&.each do |email| %>
|
7
|
+
<%= render "rails_mail/shared/email", email: email %>
|
8
|
+
<% end %>
|
9
|
+
</div>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
RailsMail::Engine.routes.draw do
|
2
|
+
scope :frontend, controller: :frontends do
|
3
|
+
get "modules/*name", action: :module, as: :frontend_module, constraints: { format: "js" }
|
4
|
+
get "static/:name", action: :static, as: :frontend_static, constraints: { format: %w[css js] }
|
5
|
+
end
|
6
|
+
|
7
|
+
resources :emails, only: [ :index, :show ] do
|
8
|
+
collection do
|
9
|
+
delete "emails/destroy_all", to: "emails#destroy_all", as: :destroy_all
|
10
|
+
end
|
11
|
+
end
|
12
|
+
root to: "emails#index"
|
13
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module RailsMail
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
source_root File.expand_path("templates", __dir__)
|
5
|
+
|
6
|
+
def copy_migration
|
7
|
+
rake "rails_mail:install:migrations"
|
8
|
+
end
|
9
|
+
|
10
|
+
def copy_initializer
|
11
|
+
template "initializer.rb", "config/initializers/rails_mail.rb"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# rubocop:disable Layout/CommentIndentation
|
2
|
+
|
3
|
+
RailsMail.configure do |config|
|
4
|
+
# Authentication setup
|
5
|
+
# if left blank, authentication is skipped
|
6
|
+
# config.authentication_callback do
|
7
|
+
# Example implementation for Authlogic:
|
8
|
+
# user_session = UserSession.find
|
9
|
+
# msg = Rails.env.development? ? 'Forbidden - make sure you have the correct permission in config/initializers/rails_mail.rb' : 'Not Found'
|
10
|
+
# raise ActionController::RoutingError.new(msg) unless user_session&.user&.admin?
|
11
|
+
# end
|
12
|
+
|
13
|
+
config.show_clear_button do
|
14
|
+
# show clear button in development
|
15
|
+
# and prefer to trim in non-development environments
|
16
|
+
# to prevent accidental deletion of emails
|
17
|
+
Rails.env.development?
|
18
|
+
end
|
19
|
+
|
20
|
+
config.trim_emails_older_than = 30.days
|
21
|
+
config.trim_emails_max_count = 1000
|
22
|
+
config.sync_via = :later
|
23
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module RailsMail
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :authentication_callback, :show_clear_button,
|
4
|
+
:trim_emails_older_than, :trim_emails_max_count, :sync_via
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@authentication_callback = nil
|
8
|
+
@show_clear_button = nil
|
9
|
+
@trim_emails_older_than = nil
|
10
|
+
@trim_emails_max_count = nil
|
11
|
+
@sync_via = :later
|
12
|
+
end
|
13
|
+
|
14
|
+
def sync_via=(value)
|
15
|
+
unless [ :now, :later ].include?(value)
|
16
|
+
raise ArgumentError, "sync_via must be :now or :later"
|
17
|
+
end
|
18
|
+
@sync_via = value
|
19
|
+
end
|
20
|
+
|
21
|
+
def authentication_callback=(callback)
|
22
|
+
unless callback.nil? || callback.respond_to?(:call)
|
23
|
+
raise ArgumentError, "authentication_callback must be nil or respond to #call"
|
24
|
+
end
|
25
|
+
@authentication_callback = callback
|
26
|
+
end
|
27
|
+
|
28
|
+
def show_clear_button=(callback)
|
29
|
+
unless callback.nil? || callback.respond_to?(:call)
|
30
|
+
raise ArgumentError, "show_clear_button must be nil or respond to #call"
|
31
|
+
end
|
32
|
+
@show_clear_button = callback
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module RailsMail
|
2
|
+
class DeliveryMethod
|
3
|
+
def initialize(options = {})
|
4
|
+
end
|
5
|
+
|
6
|
+
def deliver!(mail)
|
7
|
+
RailsMail::Email.create!(
|
8
|
+
from: mail.from&.first,
|
9
|
+
to: mail.to,
|
10
|
+
cc: mail.cc,
|
11
|
+
bcc: mail.bcc,
|
12
|
+
subject: mail.subject,
|
13
|
+
body: mail.body.to_s,
|
14
|
+
content_type: mail.content_type,
|
15
|
+
attachments: mail.attachments.map { |a| { filename: a.filename, content_type: a.content_type.split(";").first } }
|
16
|
+
)
|
17
|
+
rescue => e
|
18
|
+
Rails.logger.error("Failed to deliver email: #{e.message}")
|
19
|
+
raise e
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module RailsMail
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace RailsMail
|
4
|
+
|
5
|
+
initializer "rails_mail.add_delivery_method" do
|
6
|
+
ActiveSupport.on_load :action_mailer do
|
7
|
+
ActionMailer::Base.add_delivery_method :rails_mail, RailsMail::DeliveryMethod
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/rails_mail.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require "rails_mail/version"
|
2
|
+
require "rails_mail/engine"
|
3
|
+
require "rails_mail/delivery_method"
|
4
|
+
require "rails_mail/configuration"
|
5
|
+
|
6
|
+
module RailsMail
|
7
|
+
class << self
|
8
|
+
def configure
|
9
|
+
yield(configuration)
|
10
|
+
end
|
11
|
+
|
12
|
+
def configuration
|
13
|
+
@configuration ||= Configuration.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def reset_configuration!
|
17
|
+
@configuration = Configuration.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def authentication_callback(&block)
|
21
|
+
@authentication_callback = block if block
|
22
|
+
@authentication_callback || ->(request) { true }
|
23
|
+
end
|
24
|
+
|
25
|
+
def show_clear_button?(&block)
|
26
|
+
@show_clear_button = block if block
|
27
|
+
@show_clear_button || ->(request) { true }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# class Configuration
|
32
|
+
# def authentication_callback(&block)
|
33
|
+
# RailsMail.authentication_callback(&block)
|
34
|
+
# end
|
35
|
+
|
36
|
+
# def show_clear_button(&block)
|
37
|
+
# RailsMail.show_clear_button?(&block)
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
desc "Copy over the migration and initializer RailsMail"
|
2
|
+
namespace :rails_mail do
|
3
|
+
task :install do
|
4
|
+
Rails::Command.invoke :generate, [ "rails_mail:install" ]
|
5
|
+
end
|
6
|
+
|
7
|
+
# task :update do
|
8
|
+
# Rails::Command.invoke :generate, [ "rails_mail:update" ]
|
9
|
+
# end
|
10
|
+
end
|