mailer-log 0.1.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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +22 -0
  3. data/LICENSE +21 -0
  4. data/README.md +286 -0
  5. data/app/assets/stylesheets/mailer_log/emails.scss +18 -0
  6. data/app/controllers/mailer_log/admin/emails_controller.rb +34 -0
  7. data/app/controllers/mailer_log/admin_controller.rb +37 -0
  8. data/app/controllers/mailer_log/api/emails_controller.rb +88 -0
  9. data/app/controllers/mailer_log/api/mailers_controller.rb +15 -0
  10. data/app/controllers/mailer_log/application_controller.rb +7 -0
  11. data/app/controllers/mailer_log/assets_controller.rb +42 -0
  12. data/app/controllers/mailer_log/spa_controller.rb +13 -0
  13. data/app/controllers/mailer_log/webhooks_controller.rb +115 -0
  14. data/app/helpers/mailer_log/spa_helper.rb +48 -0
  15. data/app/jobs/mailer_log/application_job.rb +12 -0
  16. data/app/jobs/mailer_log/cleanup_job.rb +16 -0
  17. data/app/models/mailer_log/application_record.rb +7 -0
  18. data/app/models/mailer_log/current.rb +10 -0
  19. data/app/models/mailer_log/email.rb +47 -0
  20. data/app/models/mailer_log/event.rb +27 -0
  21. data/app/views/mailer_log/admin/emails/_email.html.erb +29 -0
  22. data/app/views/mailer_log/admin/emails/_filters.html.erb +58 -0
  23. data/app/views/mailer_log/admin/emails/index.html.erb +61 -0
  24. data/app/views/mailer_log/admin/emails/show.html.erb +132 -0
  25. data/app/views/mailer_log/spa/index.html.erb +21 -0
  26. data/config/locales/en.yml +5 -0
  27. data/config/routes.rb +26 -0
  28. data/db/schema.rb +17 -0
  29. data/lib/generators/mailer_log/install/install_generator.rb +33 -0
  30. data/lib/generators/mailer_log/install/templates/README +35 -0
  31. data/lib/generators/mailer_log/install/templates/create_mailer_log_tables.rb.tt +52 -0
  32. data/lib/generators/mailer_log/install/templates/initializer.rb.tt +33 -0
  33. data/lib/mailer_log/configuration.rb +33 -0
  34. data/lib/mailer_log/engine.rb +28 -0
  35. data/lib/mailer_log/mail_interceptor.rb +97 -0
  36. data/lib/mailer_log/mail_observer.rb +45 -0
  37. data/lib/mailer_log/version.rb +5 -0
  38. data/lib/mailer_log.rb +24 -0
  39. data/lib/tasks/mailer_log.rake +41 -0
  40. data/public/mailer_log/.vite/manifest.json +11 -0
  41. data/public/mailer_log/assets/index-D_66gvIL.css +1 -0
  42. data/public/mailer_log/assets/mailer_log-2Waj6tsV.js +46 -0
  43. data/public/mailer_log/index.html +13 -0
  44. metadata +139 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 62eb9e79690dbc8bfda9c264bdebc0d1da51e29f5df9d54d549e9fef7420cf3d
4
+ data.tar.gz: c9ff2c0973cdfd3514c6f9b40e102a4aa0784e21290b9b5eee0bfe51ceea9048
5
+ SHA512:
6
+ metadata.gz: 90f81eac98bbffe31bec481a156d160597a93b4e54d510c8d61784fd02aa38807723ce5cf4b6aaa622c1bd45b30be65c2a11e4a118d887757513ba67ee571cec
7
+ data.tar.gz: fab117cb20cb2368872faf733b6718fbffe1fafc51be4b58409d2f66a58cc632b20fe362194940de6dad562416db200de1c54c3dcc722fed803b3d9ffed6a7f0
data/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2024-12-14
9
+
10
+ ### Added
11
+
12
+ - Initial release
13
+ - Rails engine for capturing all outgoing emails
14
+ - PostgreSQL storage for emails with full headers, body, and metadata
15
+ - Vue.js 3 admin UI with Tailwind CSS for browsing emails
16
+ - Mailgun webhook integration for delivery tracking (delivered, opened, clicked, bounced)
17
+ - Polymorphic `accountable` association for linking emails to business objects
18
+ - Configurable call stack capture for debugging
19
+ - JSON API for programmatic access
20
+ - Filter and search capabilities (by recipient, sender, subject, mailer, status, date range)
21
+ - Email preview with iframe rendering
22
+ - Delivery events timeline
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 TrafficRunners
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,286 @@
1
+ # MailerLog
2
+
3
+ Rails engine for logging all outgoing emails with Mailgun webhook integration.
4
+
5
+ ## Features
6
+
7
+ - Automatic capture of all outgoing emails (body, headers, mailer class/action)
8
+ - Call stack saving for debugging (shows where the email was triggered from)
9
+ - Mailgun webhook integration for delivery status tracking
10
+ - Modern Vue 3 + Tailwind CSS admin UI
11
+ - Email preview in sandboxed iframe
12
+ - Polymorphic association with Organization/Account for filtering by organization
13
+ - Configurable retention period with automatic cleanup
14
+
15
+ ## Installation
16
+
17
+ ### 1. Add to Gemfile
18
+
19
+ ```ruby
20
+ gem 'mailer_log'
21
+ ```
22
+
23
+ ### 2. Run install generator
24
+
25
+ ```bash
26
+ bundle install
27
+ rails generate mailer_log:install
28
+ rails db:migrate
29
+ ```
30
+
31
+ This will:
32
+ - Create `config/initializers/mailer_log.rb` with configuration
33
+ - Copy migration to `db/migrate/`
34
+
35
+ ### 3. Add routes
36
+
37
+ ```ruby
38
+ # config/routes.rb
39
+ Rails.application.routes.draw do
40
+ mount MailerLog::Engine, at: '/mailer_log'
41
+ end
42
+ ```
43
+
44
+ ### 4. Build the frontend (if needed)
45
+
46
+ The engine includes pre-built Vue.js frontend assets. If you need to rebuild:
47
+
48
+ ```bash
49
+ # From host application
50
+ rake mailer_log:build_frontend
51
+
52
+ # Or directly
53
+ cd path/to/mailer_log/frontend
54
+ npm install
55
+ npm run build
56
+ ```
57
+
58
+ ### 5. Configure initializer
59
+
60
+ Edit `config/initializers/mailer_log.rb` to customize settings:
61
+
62
+ ```ruby
63
+ MailerLog.configure do |config|
64
+ # Email retention period (default 1 year)
65
+ config.retention_period = 1.year
66
+
67
+ # Key for Mailgun webhook verification
68
+ config.webhook_signing_key = ENV['MAILGUN_WEBHOOK_SIGNING_KEY']
69
+
70
+ # Capture call stack (useful for debugging)
71
+ config.capture_call_stack = Rails.env.development?
72
+
73
+ # Optional: Additional authentication for admin UI
74
+ # config.authenticate_with do |controller|
75
+ # controller.send(:require_super_admin!)
76
+ # end
77
+
78
+ # Optional: Resolver for organization association
79
+ # config.resolve_accountable do |email_record|
80
+ # User.find_by(email: email_record.to_addresses&.first)&.organization
81
+ # end
82
+ end
83
+ ```
84
+
85
+ ## Mailgun Webhook Setup
86
+
87
+ ### 1. Get Webhook Signing Key
88
+
89
+ 1. Log in to [Mailgun Dashboard](https://app.mailgun.com/)
90
+ 2. Go to **Sending** → **Webhooks**
91
+ 3. Copy the **HTTP webhook signing key**
92
+ 4. Add to ENV: `MAILGUN_WEBHOOK_SIGNING_KEY=your_key_here`
93
+
94
+ ### 2. Configure Webhook URL
95
+
96
+ 1. In Mailgun Dashboard, go to **Webhooks**
97
+ 2. Add webhook for each event:
98
+ - URL: `https://your-app.com/admin/email_log/webhooks/mailgun`
99
+ - Events: `delivered`, `opened`, `clicked`, `bounced`, `failed`, `dropped`, `complained`
100
+
101
+ ### 3. For Each Domain
102
+
103
+ If using multiple domains (white-label), configure webhooks for each.
104
+
105
+ ## Usage
106
+
107
+ ### Admin UI
108
+
109
+ After installation, accessible at: `/admin/email_log/admin/emails`
110
+
111
+ **Features:**
112
+ - List of all sent emails with pagination
113
+ - Filtering by: recipient, sender, subject, mailer class, status, date
114
+ - Email details view: headers, body preview, delivery events
115
+ - Call stack view (where in code the email was triggered)
116
+
117
+ ### Email Statuses
118
+
119
+ | Status | Description |
120
+ |--------|-------------|
121
+ | `pending` | Email created, waiting to be sent |
122
+ | `sent` | Email sent to Mailgun |
123
+ | `delivered` | Email delivered to recipient |
124
+ | `opened` | Email opened (tracking pixel) |
125
+ | `clicked` | Link in email clicked |
126
+ | `bounced` | Email not delivered (hard bounce) |
127
+ | `complained` | Recipient marked as spam |
128
+
129
+ ### Programmatic Access
130
+
131
+ ```ruby
132
+ # Find all emails for a user
133
+ MailerLog::Email.recipient('user@example.com')
134
+
135
+ # Find emails by mailer class
136
+ MailerLog::Email.by_mailer('UsersMailer')
137
+
138
+ # Find undelivered emails
139
+ MailerLog::Email.by_status('bounced')
140
+
141
+ # Last 10 emails
142
+ MailerLog::Email.recent.limit(10)
143
+
144
+ # Emails with events
145
+ email = MailerLog::Email.find(id)
146
+ email.events.each do |event|
147
+ puts "#{event.event_type} at #{event.occurred_at}"
148
+ end
149
+ ```
150
+
151
+ ### Cleaning Up Old Records
152
+
153
+ Add to Sidekiq cron or call periodically:
154
+
155
+ ```ruby
156
+ # Run manually
157
+ MailerLog::CleanupJob.perform_now
158
+
159
+ # Via Sidekiq
160
+ MailerLog::CleanupJob.perform_later
161
+
162
+ # In sidekiq-cron (config/schedule.yml or sidekiq.yml)
163
+ cleanup_mailer_log:
164
+ cron: '0 3 * * *' # Daily at 3:00 AM
165
+ class: MailerLog::CleanupJob
166
+ queue: low_priority
167
+ ```
168
+
169
+ ## Configuration
170
+
171
+ | Parameter | Default | Description |
172
+ |-----------|---------|-------------|
173
+ | `retention_period` | `1.year` | Email retention period |
174
+ | `webhook_signing_key` | `nil` | Key for Mailgun webhook verification |
175
+ | `capture_call_stack` | `true` | Capture call stack |
176
+ | `call_stack_depth` | `20` | Call stack depth |
177
+ | `admin_layout` | `'application'` | Layout for admin views |
178
+ | `per_page` | `25` | Records per page |
179
+
180
+ ## Troubleshooting
181
+
182
+ ### Emails Not Being Saved
183
+
184
+ 1. Check that engine is included in Gemfile and bundle install was run
185
+ 2. Check that migrations are executed: `rails db:migrate:status`
186
+ 3. Check logs for errors in `MailerLog::MailObserver`
187
+
188
+ ### Webhooks Not Working
189
+
190
+ 1. Check `MAILGUN_WEBHOOK_SIGNING_KEY` in ENV
191
+ 2. Check that URL is accessible externally (not localhost)
192
+ 3. Check logs for signature verification errors
193
+ 4. In Mailgun Dashboard, check webhook status (failed/success)
194
+
195
+ ### Events Not Linking to Emails
196
+
197
+ 1. Check that `message_id` is being saved correctly
198
+ 2. Check that header `X-Mailer-Log-Tracking-ID` is passed to Mailgun
199
+ 3. Webhook may arrive before email is saved — retry mechanism is used
200
+
201
+ ## Frontend Development
202
+
203
+ The admin UI is built with Vue 3 + Vite + Tailwind CSS.
204
+
205
+ ### Development Mode
206
+
207
+ For hot-reload during development:
208
+
209
+ ```bash
210
+ # Terminal 1: Start Rails server
211
+ rails server
212
+
213
+ # Terminal 2: Start Vite dev server
214
+ cd path/to/mailer_log/frontend
215
+ npm run dev
216
+ ```
217
+
218
+ The Vue app will automatically use the Vite dev server at `http://localhost:5173` when assets are not built.
219
+
220
+ ### Building for Production
221
+
222
+ ```bash
223
+ rake mailer_log:build_frontend
224
+ ```
225
+
226
+ Built assets are stored in `public/mailer_log/assets/`.
227
+
228
+ ### Frontend Structure
229
+
230
+ ```
231
+ frontend/
232
+ ├── src/
233
+ │ ├── api/ # API client functions
234
+ │ ├── assets/ # CSS and static assets
235
+ │ ├── components/ # Reusable Vue components
236
+ │ ├── views/ # Page components
237
+ │ ├── App.vue # Root component
238
+ │ └── main.js # Entry point
239
+ ├── index.html # HTML template
240
+ ├── package.json
241
+ ├── tailwind.config.js
242
+ └── vite.config.js
243
+ ```
244
+
245
+ ### Customizing Components
246
+
247
+ You can override Vue components (like the navbar) to match your application's style.
248
+
249
+ 1. Create an overrides directory in your host application:
250
+ ```bash
251
+ mkdir -p app/javascript/mailer_log_overrides
252
+ ```
253
+
254
+ 2. Copy and customize the component:
255
+ ```bash
256
+ cp path/to/mailer_log/frontend/src/components/AppNavbar.vue \
257
+ app/javascript/mailer_log_overrides/AppNavbar.vue
258
+ ```
259
+
260
+ 3. Build with the override path:
261
+ ```bash
262
+ MAILER_LOG_OVERRIDES_PATH=/path/to/app/javascript/mailer_log_overrides \
263
+ rake mailer_log:build_frontend
264
+ ```
265
+
266
+ **Overridable components:**
267
+ - `AppNavbar.vue` - Header/navigation bar
268
+
269
+ Example custom navbar that matches host application style:
270
+ ```vue
271
+ <template>
272
+ <nav class="your-app-navbar">
273
+ <router-link to="/">Email Log</router-link>
274
+ <!-- Your custom navigation items -->
275
+ </nav>
276
+ </template>
277
+
278
+ <script setup>
279
+ // You can import from @mailer-log/ alias to reuse engine components
280
+ // import StatusBadge from '@mailer-log/components/StatusBadge.vue'
281
+ </script>
282
+ ```
283
+
284
+ ## License
285
+
286
+ MIT
@@ -0,0 +1,18 @@
1
+ // MailerLog Email Views Styles
2
+
3
+ .mailer-log-email-preview {
4
+ width: 100%;
5
+ height: 500px;
6
+ border: 1px solid #ddd;
7
+ background: #fff;
8
+ }
9
+
10
+ .mailer-log-scrollable-content {
11
+ max-height: 500px;
12
+ overflow: auto;
13
+ }
14
+
15
+ .mailer-log-scrollable-content-sm {
16
+ max-height: 300px;
17
+ overflow: auto;
18
+ }
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MailerLog
4
+ module Admin
5
+ class EmailsController < MailerLog::AdminController
6
+ has_scope :recipient
7
+ has_scope :sender
8
+ has_scope :subject_search
9
+ has_scope :by_mailer, as: :mailer
10
+ has_scope :by_status, as: :status
11
+ has_scope :date_from
12
+ has_scope :date_to
13
+
14
+ def index
15
+ @emails = apply_scopes(MailerLog::Email.order(created_at: :desc))
16
+ .page(params[:page])
17
+ .per(params[:per] || 25)
18
+ @mailers = MailerLog::Email.distinct.pluck(:mailer_class).compact.sort
19
+ end
20
+
21
+ def show
22
+ @email = MailerLog::Email.find(params[:id])
23
+ end
24
+
25
+ def preview
26
+ @email = MailerLog::Email.find(params[:id])
27
+ # rubocop:disable Rails/OutputSafety
28
+ render html: @email.html_body&.html_safe, layout: false
29
+ # rubocop:enable Rails/OutputSafety
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MailerLog
4
+ class AdminController < ::AdminsController
5
+ prepend HasScope
6
+
7
+ # Expose main app routes to views for navbar/layout links
8
+ helper Rails.application.routes.url_helpers
9
+
10
+ before_action :authenticate_mailer_log!
11
+
12
+ # Make main_app proxy available in views
13
+ helper_method :main_app
14
+
15
+ # Override url_options to prevent main app routes from being prefixed
16
+ # with the engine's mount path
17
+ def url_options
18
+ script_name = request.env['SCRIPT_NAME']
19
+ return super unless script_name&.include?('mailer_log') || script_name&.include?('email_log')
20
+
21
+ super.merge(script_name: '')
22
+ end
23
+
24
+ private
25
+
26
+ def authenticate_mailer_log!
27
+ return unless MailerLog.configuration.authenticate_with_proc
28
+
29
+ MailerLog.configuration.authenticate_with_proc.call(self)
30
+ end
31
+
32
+ def mailer_log_engine
33
+ MailerLog::Engine.routes.url_helpers
34
+ end
35
+ helper_method :mailer_log_engine
36
+ end
37
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MailerLog
4
+ module Api
5
+ class EmailsController < MailerLog::AdminController
6
+ skip_before_action :verify_authenticity_token, only: [:index, :show]
7
+
8
+ def index
9
+ emails = apply_filters(MailerLog::Email.order(created_at: :desc))
10
+ .page(params[:page])
11
+ .per(params[:per] || 25)
12
+
13
+ render json: {
14
+ emails: emails.map { |e| email_json(e) },
15
+ total_count: emails.total_count,
16
+ total_pages: emails.total_pages,
17
+ current_page: emails.current_page
18
+ }
19
+ end
20
+
21
+ def show
22
+ email = MailerLog::Email.find(params[:id])
23
+
24
+ render json: {
25
+ email: email_json(email, include_body: true, include_events: true)
26
+ }
27
+ end
28
+
29
+ private
30
+
31
+ def apply_filters(scope)
32
+ scope = scope.recipient(params[:recipient]) if params[:recipient].present?
33
+ scope = scope.sender(params[:sender]) if params[:sender].present?
34
+ scope = scope.subject_search(params[:subject_search]) if params[:subject_search].present?
35
+ scope = scope.by_mailer(params[:mailer]) if params[:mailer].present?
36
+ scope = scope.by_status(params[:status]) if params[:status].present?
37
+ scope = scope.date_from(params[:date_from]) if params[:date_from].present?
38
+ scope = scope.date_to(params[:date_to]) if params[:date_to].present?
39
+ scope
40
+ end
41
+
42
+ def email_json(email, include_body: false, include_events: false)
43
+ data = {
44
+ id: email.id,
45
+ tracking_id: email.tracking_id,
46
+ message_id: email.message_id,
47
+ mailer_class: email.mailer_class,
48
+ mailer_action: email.mailer_action,
49
+ from_address: email.from_address,
50
+ to_addresses: email.to_addresses,
51
+ cc_addresses: email.cc_addresses,
52
+ bcc_addresses: email.bcc_addresses,
53
+ subject: email.subject,
54
+ domain: email.domain,
55
+ status: email.status,
56
+ delivered_at: email.delivered_at,
57
+ opened_at: email.opened_at,
58
+ clicked_at: email.clicked_at,
59
+ bounced_at: email.bounced_at,
60
+ created_at: email.created_at,
61
+ updated_at: email.updated_at
62
+ }
63
+
64
+ if include_body
65
+ data[:html_body] = email.html_body
66
+ data[:text_body] = email.text_body
67
+ data[:headers] = email.headers
68
+ data[:call_stack] = email.call_stack
69
+ end
70
+
71
+ if include_events
72
+ data[:events] = email.events.recent.map do |event|
73
+ {
74
+ id: event.id,
75
+ event_type: event.event_type,
76
+ occurred_at: event.occurred_at,
77
+ recipient: event.recipient,
78
+ ip_address: event.ip_address,
79
+ user_agent: event.user_agent
80
+ }
81
+ end
82
+ end
83
+
84
+ data
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MailerLog
4
+ module Api
5
+ class MailersController < MailerLog::AdminController
6
+ skip_before_action :verify_authenticity_token
7
+
8
+ def index
9
+ mailers = MailerLog::Email.distinct.pluck(:mailer_class).compact.sort
10
+
11
+ render json: { mailers: mailers }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MailerLog
4
+ class ApplicationController < ActionController::Base
5
+ protect_from_forgery with: :exception
6
+ end
7
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MailerLog
4
+ class AssetsController < ActionController::Base
5
+ skip_forgery_protection
6
+
7
+ def show
8
+ file_path = MailerLog::Engine.root.join('public', 'mailer_log', 'assets', params[:path])
9
+
10
+ if File.exist?(file_path) && file_path.to_s.start_with?(MailerLog::Engine.root.join('public', 'mailer_log').to_s)
11
+ content_type = content_type_for(file_path)
12
+ send_file file_path, type: content_type, disposition: 'inline'
13
+ else
14
+ head :not_found
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def content_type_for(file_path)
21
+ extension = File.extname(file_path).downcase
22
+ case extension
23
+ when '.js'
24
+ 'application/javascript'
25
+ when '.css'
26
+ 'text/css'
27
+ when '.map'
28
+ 'application/json'
29
+ when '.svg'
30
+ 'image/svg+xml'
31
+ when '.png'
32
+ 'image/png'
33
+ when '.jpg', '.jpeg'
34
+ 'image/jpeg'
35
+ when '.woff', '.woff2'
36
+ 'font/woff2'
37
+ else
38
+ 'application/octet-stream'
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MailerLog
4
+ class SpaController < MailerLog::AdminController
5
+ helper MailerLog::SpaHelper
6
+
7
+ layout false
8
+
9
+ def index
10
+ render :index
11
+ end
12
+ end
13
+ end