rails_courrier 0.5.0 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c27079a012b10a2af5545069b4fd95df7fee48f90ca676d556f6fbe0e5c14c0
4
- data.tar.gz: 7624375dfacce56335d8cd31a4a896be095fdd23bc1d66e7238470221661cad5
3
+ metadata.gz: 4b5250c19187be9e82ca4e09ca8e93d7c033581ebbdc649cbb63720683876dd9
4
+ data.tar.gz: b75dccdb78ae7b06714c76b51bf17cbe3df5cb9f5d4b4cddce8910b0c3aad684
5
5
  SHA512:
6
- metadata.gz: 79f5e0e925e3d9513422e749b390a5e4e57f2d423f062657aae3b1fd4ac13214725fa5ae7fb098885a928db77b9c6297b9493d65f88d3283e13e6ff0273ac006
7
- data.tar.gz: 706a198bc22cf183ba6cd02581c622c0d3b790c17bd5e8c6f7a8b7d60809d38bf22a22920feab28e4fe46045660436a1ec050b144063ffc1db648556c3b58544
6
+ metadata.gz: ccc0fc249bb46a36b1581c450d5b358b875e6155226a4f72e3d54847d34a237b93aedcaf37b3c8e521b4429f78184b2d5a7c80296bbdfa6a71423a23abdbda00
7
+ data.tar.gz: 21eec3ae5f91d04f211e2fb4ac9000eadea726a2cc241ab73be6a1d6d424a08dd3331f4c8b5584411b6dc5072fce5a05bef571dae094a48f87b9a0b8e807c86a
data/Gemfile CHANGED
@@ -12,4 +12,5 @@ group :development, :test do
12
12
  gem "rake", "~> 13.2", ">= 13.2.1"
13
13
  gem "minitest", "~> 5.25", ">= 5.25.5"
14
14
  gem "debug", "~> 1.9", ">= 1.9.2"
15
+ gem "propshaft"
15
16
  end
data/Gemfile.lock CHANGED
@@ -1,11 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rails_courrier (0.5.0)
4
+ rails_courrier (1.0.0)
5
5
  actionpack (>= 7.0)
6
6
  actionview (>= 7.0)
7
7
  activejob (>= 7.0)
8
- courrier (~> 0.11.0)
8
+ courrier (~> 1.0.0)
9
9
  launchy (>= 3.1, < 4)
10
10
  railties (>= 7.0)
11
11
 
@@ -54,7 +54,7 @@ GEM
54
54
  logger (~> 1.5)
55
55
  concurrent-ruby (1.3.6)
56
56
  connection_pool (3.0.2)
57
- courrier (0.11.0)
57
+ courrier (1.0.0)
58
58
  logger (>= 1.5, < 3)
59
59
  nokogiri (>= 1.18, < 2)
60
60
  crass (1.0.6)
@@ -111,6 +111,10 @@ GEM
111
111
  prettyprint
112
112
  prettyprint (0.2.0)
113
113
  prism (1.9.0)
114
+ propshaft (1.3.2)
115
+ actionpack (>= 7.0.0)
116
+ activesupport (>= 7.0.0)
117
+ rack
114
118
  psych (5.4.0)
115
119
  date
116
120
  stringio
@@ -206,6 +210,7 @@ PLATFORMS
206
210
  DEPENDENCIES
207
211
  debug (~> 1.9, >= 1.9.2)
208
212
  minitest (~> 5.25, >= 5.25.5)
213
+ propshaft
209
214
  rails_courrier!
210
215
  rake (~> 13.2, >= 13.2.1)
211
216
  standard (~> 1.49)
data/README.md CHANGED
@@ -2,16 +2,9 @@
2
2
 
3
3
  Rails-specific features for [Courrier](https://github.com/Rails-Designer/courrier), the API-powered email delivery gem for Ruby.
4
4
 
5
- ![Preview of the Rails Courrier inbox with a list of images on the left and preview of an email on the right](https://raw.githubusercontent.com/Rails-Designer/rails_courrier/HEAD/.github/rails_courrier_inbox.jpg)
5
+ ![Preview of the Rails Courrier inbox with a list of images on the left and preview of an email on the right](https://raw.githubusercontent.com/Rails-Designer/rails_courrier/HEAD/.github/rails-courrier-inbox.jpg)
6
6
 
7
7
 
8
- ## Installation
9
-
10
- Add to your Gemfile:
11
- ```bash
12
- bundle add rails_courrier
13
- ```
14
-
15
8
  <a href="https://railsdesigner.com/" target="_blank">
16
9
  <picture>
17
10
  <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Rails-Designer/courrier/HEAD/.github/logo-dark.svg">
@@ -23,11 +16,22 @@ bundle add rails_courrier
23
16
  **Sponsored By [Rails Designer](https://railsdesigner.com/)**
24
17
 
25
18
 
19
+ ## Installation
20
+
21
+ Add to your Gemfile:
22
+ ```bash
23
+ bundle add rails_courrier
24
+ ```
25
+
26
+
26
27
  ## Setup
27
28
 
28
29
  Generate the initializer:
29
30
  ```bash
30
31
  bin/rails generate courrier:install
32
+
33
+ # Or pass a provider to pre-fill provider-specific config:
34
+ bin/rails generate courrier:install --provider=mailpace
31
35
  ```
32
36
 
33
37
  This creates `config/initializers/courrier.rb`.
@@ -39,7 +43,7 @@ bin/rails generate courrier:email Order
39
43
 
40
44
  This creates `app/emails/order_email.rb`.
41
45
 
42
- Mount the engine for inbox previews:
46
+ Mount the engine to browse inbox previews in your browser:
43
47
  ```ruby
44
48
  # config/routes.rb
45
49
  mount Courrier::Engine => "/courrier"
@@ -51,14 +55,14 @@ mount Courrier::Engine => "/courrier"
51
55
  ### Send emails
52
56
 
53
57
  ```ruby
54
- OrderEmail.deliver to: "recipient@example.com"
58
+ OrderEmail.deliver to: "recipient@railsdesigner.com"
55
59
  ```
56
60
 
57
61
 
58
62
  ### Deliver later via ActiveJob
59
63
 
60
64
  ```ruby
61
- OrderEmail.deliver_later to: "recipient@example.com"
65
+ OrderEmail.deliver_later to: "recipient@railsdesigner.com"
62
66
  ```
63
67
 
64
68
  Configure queue options in the email class:
@@ -78,16 +82,11 @@ Preview emails in your browser:
78
82
  ```ruby
79
83
  # config/initializers/courrier.rb
80
84
  Courrier.configure do |config|
81
- config.email = { provider: "inbox" }
85
+ config.email = { provider: "inbox", auto_open: true }
82
86
  end
83
87
  ```
84
88
 
85
- Enable auto-open:
86
-
87
- ```ruby
88
- config.email = { provider: "inbox" }
89
- config.inbox.auto_open = true
90
- ```
89
+ The `auto_open` option opens each sent email in your default browser automatically.
91
90
 
92
91
  Clear inbox files:
93
92
 
@@ -108,6 +107,49 @@ end
108
107
  ```
109
108
 
110
109
 
110
+ ### Monitoring
111
+
112
+ Rails Courrier publishes delivery lifecycle events via `ActiveSupport::Notifications`:
113
+
114
+ ```ruby
115
+ ActiveSupport::Notifications.subscribe("delivery.courrier") do |event|
116
+ Rails.logger.info "[Courrier] #{event.payload[:email]} delivered in #{event.duration}ms"
117
+ end
118
+ ```
119
+
120
+ #### Available events
121
+
122
+ | Event | When it fires | Payload |
123
+ |-------|---------------|---------|
124
+ | `delivery.courrier` | Email delivered successfully | `email`, `options` |
125
+ | `delivery_failed.courrier` | Delivery raised an exception | `email`, `options`, `exception` |
126
+
127
+
128
+ ### I18n
129
+
130
+ Use the `t` helper in email classes and ERB templates, scoped under `courrier.email.<class_name>`:
131
+ ```yaml
132
+ # config/locales/courrier/en.yml
133
+ en:
134
+ courrier:
135
+ email:
136
+ order_email:
137
+ subject: "Your order is ready!"
138
+ text:
139
+ greeting: "Hello %{name}, your order is ready."
140
+ ```
141
+
142
+ ```ruby
143
+ class OrderEmail < Courrier::Email
144
+ def subject = t(".subject")
145
+
146
+ def text = t(".text.greeting", name: options.to)
147
+ end
148
+ ```
149
+
150
+ Full translation keys (e.g. `t("shared.greeting")`) pass through unscoped.
151
+
152
+
111
153
  ## Documentation
112
154
 
113
155
  See the [Courrier README](https://github.com/Rails-Designer/courrier) for full configuration, providers, layouts, templates and more.
@@ -0,0 +1,145 @@
1
+ @layer reset, base, components;
2
+
3
+ @layer reset {
4
+ *, *::before, *::after { box-sizing: border-box; }
5
+ * { margin: 0; padding: 0; }
6
+ }
7
+
8
+ @layer base {
9
+ :root {
10
+ --gray-50: oklch(98.7% .002 197.1);
11
+ --gray-100: oklch(96.3% .002 197.1);
12
+ --gray-200: oklch(92.5% .005 214.3);
13
+ --gray-300: oklch(87.2% .007 219.6);
14
+ --gray-400: oklch(72.3% .014 214.4);
15
+ --gray-500: oklch(56% .021 213.5);
16
+ --gray-600: oklch(45% .017 213.2);
17
+ --gray-700: oklch(37.8% .015 216);
18
+ --gray-800: oklch(27.5% .011 216.9);
19
+ --gray-900: oklch(21.8% .008 223.9);
20
+ --gray-950: oklch(14.8% .004 228.8);
21
+ }
22
+
23
+ body {
24
+ margin: 0;
25
+ padding: 0;
26
+ font-size: 16px;
27
+ line-height: 1.5;
28
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
29
+ background: white;
30
+ }
31
+ }
32
+
33
+ @layer components {
34
+ .previews-layout {
35
+ display: flex;
36
+ column-gap: 1rem;
37
+ }
38
+
39
+ .preview-pane { flex: 1; }
40
+
41
+ .sidebar {
42
+ display: flex;
43
+ flex-direction: column;
44
+ position: sticky;
45
+ inset-block-start: 0;
46
+ padding: .5rem;
47
+ width: min(350px, 33.3333%);
48
+ height: 100dvh;
49
+ border-inline-end: 1px solid var(--gray-200);
50
+
51
+ .options {
52
+ position: sticky;
53
+ inset-block-end: 0;
54
+ }
55
+
56
+ .btn-clear {
57
+ display: flex;
58
+ align-items: center;
59
+ justify-content: center;
60
+ column-gap: .375rem;
61
+ width: 100%;
62
+ padding: .5rem;
63
+ font-size: .875rem;
64
+ line-height: 1.25rem;
65
+ font-weight: 400;
66
+ color: var(--gray-800);
67
+ background: var(--gray-100);
68
+ border: none;
69
+ border-radius: .375rem;
70
+ cursor: default;
71
+
72
+ &:hover { background: var(--gray-200); }
73
+ &:active { transform: scale(.98); }
74
+
75
+ [data-slot="icon"] {
76
+ width: .875rem; height: auto;
77
+ aspect-ratio: 1;
78
+ color: oklch(from currentColor l c h / .75);
79
+ }
80
+ }
81
+ }
82
+
83
+ .email-previews {
84
+ display: flex;
85
+ flex-direction: column;
86
+ flex: 1;
87
+ row-gap: .5rem;
88
+ width: 100%;
89
+ overflow-y: auto;
90
+
91
+ .empty {
92
+ display: none;
93
+ font-size: .875rem;
94
+ font-weight: 300;
95
+ color: var(--gray-500);
96
+ text-align: center;
97
+
98
+ &:only-child { display: block; }
99
+ }
100
+ }
101
+
102
+ .email-preview {
103
+ display: flex;
104
+ flex-direction: row;
105
+ column-gap: .5rem;
106
+ width: 100%;
107
+ padding: .5rem .75rem;
108
+ text-decoration: none;
109
+ border-radius: .25rem;
110
+
111
+ &:hover { background: var(--gray-100); }
112
+
113
+ .avatar {
114
+ flex-shrink: 0;
115
+ padding: .25rem;
116
+ width: 1.25rem; height: 1.25rem;
117
+ background: var(--gray-300);
118
+ color: var(--gray-700);
119
+ border-radius: 9999px;
120
+ transform: translateY(.25em);
121
+ }
122
+
123
+ .content {
124
+ line-height: 1.25rem;
125
+ }
126
+
127
+ .recipient {
128
+ line-height: 1.5rem;
129
+ font-size: .875rem;
130
+ font-weight: 400;
131
+ color: var(--gray-700);
132
+ }
133
+
134
+ .subject {
135
+ margin-block-start: .125rem;
136
+ overflow: hidden;
137
+ display: -webkit-box;
138
+ -webkit-box-orient: vertical;
139
+ -webkit-line-clamp: 1;
140
+ font-size: .875rem;
141
+ font-weight: 300;
142
+ color: var(--gray-600);
143
+ }
144
+ }
145
+ }
@@ -0,0 +1,260 @@
1
+ <!--
2
+ {
3
+ "to": "<%= email %>",
4
+ "subject": "<%= @options.subject %>"
5
+ }
6
+ -->
7
+
8
+ <!DOCTYPE html>
9
+ <html>
10
+ <head>
11
+ <meta charset="UTF-8">
12
+ <title><%= @options.subject %></title>
13
+ <style>
14
+ :root {
15
+ --gray-50: oklch(98.7% .002 197.1);
16
+ --gray-100: oklch(96.3% .002 197.1);
17
+ --gray-200: oklch(92.5% .005 214.3);
18
+ --gray-300: oklch(87.2% .007 219.6);
19
+ --gray-400: oklch(72.3% .014 214.4);
20
+ --gray-500: oklch(56% .021 213.5);
21
+ --gray-600: oklch(45% .017 213.2);
22
+ --gray-700: oklch(37.8% .015 216);
23
+ --gray-800: oklch(27.5% .011 216.9);
24
+ --gray-900: oklch(21.8% .008 223.9);
25
+ --gray-950: oklch(14.8% .004 228.8);
26
+ }
27
+
28
+ * { margin: 0; padding: 0; box-sizing: border-box; }
29
+
30
+ body {
31
+ font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
32
+ font-size: 1rem;
33
+ line-height: 1.5;
34
+ color: var(--gray-800);
35
+ background: var(--gray-50);
36
+ }
37
+
38
+ article {
39
+ max-width: 65rem;
40
+ margin-inline: auto;
41
+ }
42
+
43
+ .header {
44
+ padding: 1.25rem 1rem 0;
45
+ border-block-end: 1px solid var(--gray-100);
46
+ }
47
+
48
+ .metadata {
49
+ display: flex;
50
+ gap: .75rem;
51
+ }
52
+
53
+ .avatar {
54
+ flex-shrink: 0;
55
+ padding: .25rem;
56
+ width: 2rem;
57
+ height: 2rem;
58
+ background: var(--gray-200);
59
+ color: var(--gray-600);
60
+ border-radius: 9999px;
61
+ }
62
+
63
+ .meta {
64
+ flex: 1;
65
+ min-width: 0;
66
+
67
+ .row {
68
+ display: flex;
69
+ gap: .5rem;
70
+ font-size: .875rem;
71
+ line-height: 1.6;
72
+
73
+ .label {
74
+ flex-shrink: 0;
75
+ width: 3.5rem;
76
+ color: var(--gray-500);
77
+ font-weight: 300;
78
+ }
79
+
80
+ .value {
81
+ color: var(--gray-700);
82
+ min-width: 0;
83
+ overflow: hidden;
84
+ text-overflow: ellipsis;
85
+ white-space: nowrap;
86
+ }
87
+ }
88
+ }
89
+
90
+ .subject {
91
+ margin-block-start: .75rem;
92
+ padding-block: .75rem;
93
+ border-block-start: 1px solid var(--gray-100);
94
+ display: flex;
95
+ gap: .5rem;
96
+
97
+ .label {
98
+ flex-shrink: 0;
99
+ width: 3.5rem;
100
+ font-size: .875rem;
101
+ color: var(--gray-500);
102
+ font-weight: 400;
103
+ line-height: 1.75rem;
104
+ }
105
+
106
+ .text {
107
+ flex: 1;
108
+ font-size: 1.125rem;
109
+ font-weight: 500;
110
+ color: var(--gray-900);
111
+ line-height: 1.75rem;
112
+ }
113
+
114
+ .datetime {
115
+ flex-shrink: 0;
116
+ font-size: .75rem;
117
+ font-weight: 300;
118
+ color: var(--gray-500);
119
+ line-height: 1.75rem;
120
+ }
121
+ }
122
+
123
+ .email {
124
+ padding-block-start: 1rem;
125
+ padding-inline: 1rem;
126
+ }
127
+
128
+ .preview-toggle {
129
+ display: none;
130
+ }
131
+
132
+ .toggle {
133
+ display: flex;
134
+ gap: .25rem;
135
+ }
136
+
137
+ .toggle-label {
138
+ display: inline-flex;
139
+ padding: .125rem .75rem;
140
+ font-size: .75rem;
141
+ line-height: 1.25rem;
142
+ font-weight: 400;
143
+ color: var(--gray-600);
144
+ background: var(--gray-50);
145
+ border: 1px solid var(--gray-100);
146
+ border-radius: .375rem;
147
+ cursor: pointer;
148
+ transition: all .15s;
149
+
150
+ &:hover { border-color: var(--gray-200); }
151
+ &:active { transform: scale(.98); }
152
+
153
+ .view-html { display: inline; }
154
+ .view-text { display: none; }
155
+ }
156
+
157
+ .preview-toggle:checked ~ .toggle .toggle-label {
158
+ background: var(--gray-100);
159
+ border-color: var(--gray-300);
160
+
161
+ .view-html { display: none; }
162
+ .view-text { display: inline; }
163
+ }
164
+
165
+ .preview-toggle[disabled] ~ .toggle .toggle-label {
166
+ opacity: .5;
167
+ cursor: not-allowed;
168
+ pointer-events: none;
169
+ }
170
+
171
+ .preview-text {
172
+ margin-block-start: 1.25rem;
173
+
174
+ pre {
175
+ margin: 0;
176
+ white-space: pre-wrap;
177
+ font-size: .875rem;
178
+ line-height: 1.6;
179
+ color: var(--gray-700);
180
+ font-family: ui-monospace, "SF Mono", "Cascadia Code", "Fira Code", Menlo, monospace;
181
+ }
182
+ }
183
+
184
+ .preview-html {
185
+ display: none;
186
+ margin-block-start: 1rem;
187
+
188
+ p + p { margin-block-start: 1rem; }
189
+ ul, ol { padding-inline-start: 1.5rem; }
190
+ li + li { margin-block-start: .375rem; }
191
+ h1, h2, h3, h4 { line-height: 1.3; color: var(--gray-900); }
192
+ h1 { font-size: 1.5rem; margin-block: 1rem .5rem; }
193
+ h2 { font-size: 1.25rem; margin-block: .875rem .5rem; }
194
+ h3 { font-size: 1.125rem; margin-block: .75rem .375rem; }
195
+ blockquote {
196
+ margin-inline-start: 0;
197
+ padding-inline-start: 1rem;
198
+ border-inline-start: 3px solid var(--gray-300);
199
+ color: var(--gray-600);
200
+ }
201
+ a { color: var(--gray-700); text-decoration: underline; }
202
+ hr { margin-block: 1.5rem; border: none; border-block-start: 1px solid var(--gray-200); }
203
+ pre { overflow-x: auto; }
204
+ img { max-width: 100%; height: auto; }
205
+ }
206
+
207
+ .preview-toggle:checked ~ .preview-text { display: none; }
208
+ .preview-toggle:checked ~ .preview-html { display: block; }
209
+ </style>
210
+ </head>
211
+
212
+ <body>
213
+ <article>
214
+ <header class="header">
215
+ <div class="metadata">
216
+ <svg class="avatar" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
217
+ <path d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z"/>
218
+ </svg>
219
+
220
+ <div class="meta">
221
+ <div class="row">
222
+ <span class="label">From</span>
223
+ <span class="value"><%= @options.respond_to?(:from) ? @options.from : "—" %></span>
224
+ </div>
225
+ <div class="row">
226
+ <span class="label">To</span>
227
+ <span class="value"><%= name ? "#{name} <#{email}>" : email %></span>
228
+ </div>
229
+ </div>
230
+ </div>
231
+
232
+ <div class="subject">
233
+ <span class="label">Subject</span>
234
+ <span class="text"><%= @options.subject %></span>
235
+ <time class="datetime" datetime="<%= Time.now.strftime("%Y-%m-%d %H:%M:%S %z") %>">
236
+ <%= Time.now.strftime("%Y-%m-%d %H:%M:%S %z") %>
237
+ </time>
238
+ </div>
239
+ </header>
240
+
241
+ <div class="email">
242
+ <input type="checkbox" id="preview-toggle" class="preview-toggle" <%= html ? "" : "disabled" %>>
243
+ <div class="toggle">
244
+ <label for="preview-toggle" class="toggle-label">
245
+ <span class="view-html">View HTML</span>
246
+ <span class="view-text">View Text</span>
247
+ </label>
248
+ </div>
249
+
250
+ <div class="preview-text">
251
+ <pre><%= text || "(no text content)" %></pre>
252
+ </div>
253
+
254
+ <div class="preview-html">
255
+ <%= html || "<p>(no HTML content)</p>" %>
256
+ </div>
257
+ </div>
258
+ </article>
259
+ </body>
260
+ </html>
@@ -4,112 +4,15 @@
4
4
  <meta charset="UTF-8">
5
5
  <title>Courrier Inbox</title>
6
6
 
7
- <style>
8
- *, *::before, *::after { box-sizing: border-box; }
9
- * { margin: 0; padding: 0; }
10
- body { margin: 0; padding: 0; font-size: 16px; line-height: 1.5; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; background-color: rgb(241 245 249); }
11
-
12
- .sidebar {
13
- display: flex;
14
- flex-direction: column;
15
- position: sticky;
16
- top: 0;
17
- padding: .5rem;
18
- width: min(350px, 33.3333%); height: 100dvh;
19
- border-right: 1px solid rgb(226 232 240);
20
- }
21
-
22
- .sidebar__options {
23
- position: sticky;
24
- bottom: 0;
25
- padding: .5rem;
26
- background-color: rgb(241 245 249);
27
- }
28
-
29
- .sidebar__btn-clear {
30
- border: none;
31
- display: flex;
32
- align-items: center;
33
- justify-content: center;
34
- column-gap: .25rem;
35
- width: 100%;
36
- padding: .5rem;
37
- font-size: .875rem; line-height: 1.25rem;
38
- font-weight: 500;
39
- color: rgb(71 85 105);
40
- background-color: rgb(226 232 240);
41
- border-radius: .5rem;
42
- cursor: default;
43
-
44
- &:hover { background-color: rgb(203 213 225); }
45
- &:active { transform: scale(.98); }
46
-
47
- [data-slot="icon"] { width: .875rem; height: .875rem; }
48
- }
49
-
50
- .email-previews {
51
- display: flex;
52
- flex-direction: column;
53
- flex: 1;
54
- row-gap: .5rem;
55
- overflow-y: auto;
56
- width: 100%;
57
- }
58
-
59
- .email-preview {
60
- display: flex;
61
- flex-direction: row;
62
- column-gap: .5rem;
63
- width: 100%;
64
- padding: .5rem .75rem;
65
- text-decoration: none;
66
- border-radius: .5rem;
67
-
68
- &:hover { background-color: rgb(226 232 240 / .75); }
69
- }
70
-
71
- .email-preview--empty {
72
- display: none;
73
- text-align: center;
74
-
75
- &:only-child { display: block; }
76
- }
77
-
78
- .email-preview__avatar {
79
- flex-shrink: 0;
80
- padding: .25rem;
81
- width: 1.5rem; height: 1.5rem;
82
- background-color: rgb(203 213 225);
83
- color: rgb(51, 65, 85);
84
- border-radius: 9999px;
85
- }
86
-
87
- .email-preview__content {
88
- font-size: .875rem;
89
- line-height: 1.25rem;
90
- }
91
-
92
- .email-preview__recipient {
93
- font-size: 1rem; line-height: 1.5rem;
94
- font-weight: 600;
95
- letter-spacing: -.025em;
96
- color: rgb(51 65 85);
97
- }
98
-
99
- .email-preview__subject {
100
- margin-top: .125rem;
101
- overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1;
102
- color: rgb(100 116 139);
103
- }
104
- </style>
7
+ <%= stylesheet_link_tag "courrier/application", media: "all" %>
105
8
  </head>
106
9
 
107
10
  <body>
108
- <div style="display: flex; column-gap: 1rem;">
11
+ <div class="previews-layout">
109
12
  <aside class="sidebar">
110
13
  <ul class="email-previews">
111
- <li class="email-preview--empty">
112
- <p class="email-preview__subject">
14
+ <li class="empty">
15
+ <p class="subject">
113
16
  No emails yet…
114
17
  </p>
115
18
  </li>
@@ -117,16 +20,16 @@
117
20
  <% @emails.each do |email| %>
118
21
  <li>
119
22
  <%= link_to courrier.preview_path(email.filename), data: {remote: true}, class: "email-preview" do %>
120
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon" class="email-preview__avatar">
23
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon" class="avatar">
121
24
  <path fill-rule="evenodd" d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z" clip-rule="evenodd"/>
122
25
  </svg>
123
26
 
124
- <div class="email-preview__content">
125
- <small class="email-preview__recipient">
27
+ <div class="content">
28
+ <small class="recipient">
126
29
  <%= email.metadata.to %>
127
30
  </small>
128
31
 
129
- <p class="email-preview__subject">
32
+ <p class="subject">
130
33
  <%= email.metadata.subject || "No subject" %>
131
34
  </p>
132
35
  </div>
@@ -135,8 +38,8 @@
135
38
  <% end %>
136
39
  </ul>
137
40
 
138
- <div class="sidebar__options">
139
- <%= button_to courrier.cleanup_path, class: "sidebar__btn-clear" do %>
41
+ <div class="options">
42
+ <%= button_to courrier.cleanup_path, class: "btn-clear" do %>
140
43
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon">
141
44
  <path fill-rule="evenodd" d="M16.5 4.478v.227a48.816 48.816 0 0 1 3.878.512.75.75 0 1 1-.256 1.478l-.209-.035-1.005 13.07a3 3 0 0 1-2.991 2.77H8.084a3 3 0 0 1-2.991-2.77L4.087 6.66l-.209.035a.75.75 0 0 1-.256-1.478A48.567 48.567 0 0 1 7.5 4.705v-.227c0-1.564 1.213-2.9 2.816-2.951a52.662 52.662 0 0 1 3.369 0c1.603.051 2.815 1.387 2.815 2.951Zm-6.136-1.452a51.196 51.196 0 0 1 3.273 0C14.39 3.05 15 3.684 15 4.478v.113a49.488 49.488 0 0 0-6 0v-.113c0-.794.609-1.428 1.364-1.452Zm-.355 5.945a.75.75 0 1 0-1.5.058l.347 9a.75.75 0 1 0 1.499-.058l-.346-9Zm5.48.058a.75.75 0 1 0-1.498-.058l-.347 9a.75.75 0 0 0 1.5.058l.345-9Z" clip-rule="evenodd"/>
142
45
  </svg>
@@ -146,8 +49,7 @@
146
49
  </div>
147
50
  </aside>
148
51
 
149
- <article style="flex: 1;" id="email-preview">
150
- </article>
52
+ <main id="email"></main>
151
53
  </div>
152
54
 
153
55
  <script>
@@ -158,7 +60,10 @@
158
60
  fetch(link.href)
159
61
  .then(response => response.text())
160
62
  .then(html => {
161
- document.getElementById("email-preview").innerHTML = html;
63
+ const preview = document.getElementById("email");
64
+
65
+ if (!preview.shadowRoot) preview.attachShadow({mode: "open"});
66
+ preview.shadowRoot.innerHTML = html;
162
67
  });
163
68
  });
164
69
  });
data/bin/rails ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails gems
3
+ # installed from the root of your application.
4
+
5
+ ENGINE_ROOT = File.expand_path("..", __dir__)
6
+ ENGINE_PATH = File.expand_path("../lib/fuik/engine", __dir__)
7
+ APP_PATH = File.expand_path("../test/dummy/config/application", __dir__)
8
+
9
+ # Set up gems listed in the Gemfile.
10
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
11
+ require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
12
+
13
+ require "rails/all"
14
+ require "rails/engine/commands"
@@ -3,12 +3,11 @@
3
3
  module Courrier
4
4
  class Configuration
5
5
  class Inbox
6
- attr_accessor :destination, :auto_open, :template_path
6
+ attr_accessor :destination, :template_path
7
7
 
8
8
  def initialize
9
9
  @destination = default_destination
10
- @auto_open = false
11
- @template_path = File.expand_path("../email/providers/inbox/default.html.erb", __dir__)
10
+ @template_path = File.expand_path("../../../app/views/courrier/previews/default.html.erb", __dir__)
12
11
  end
13
12
 
14
13
  private
@@ -17,5 +16,9 @@ module Courrier
17
16
  Rails.root.join("tmp", "courrier", "emails").to_s
18
17
  end
19
18
  end
19
+
20
+ def inbox
21
+ @inbox ||= Inbox.new
22
+ end
20
23
  end
21
24
  end
@@ -4,7 +4,12 @@ module Courrier
4
4
  class Email
5
5
  class DeliveryJob < ActiveJob::Base
6
6
  def perform(email_class_name, **options)
7
- Object.const_get(email_class_name).new(**options).deliver_now
7
+ Object.const_get(email_class_name).new(**options).deliver_now.tap do
8
+ ActiveSupport::Notifications.instrument("delivery.courrier", email: email_class_name, options: options)
9
+ end
10
+ rescue => exception
11
+ ActiveSupport::Notifications.instrument("delivery_failed.courrier", email: email_class_name, options: options, exception: [exception.class.name, exception.message])
12
+ raise
8
13
  end
9
14
  end
10
15
  end
@@ -15,7 +15,7 @@ module Courrier
15
15
 
16
16
  File.write(file_path, ERB.new(File.read(config.template_path)).result(binding))
17
17
 
18
- Launchy.open(file_path) if config.auto_open
18
+ Launchy.open(file_path) if Courrier.configuration.email[:auto_open]
19
19
 
20
20
  "📮 Email saved to #{file_path} and #{email_destination}"
21
21
  end
@@ -47,7 +47,7 @@ module Courrier
47
47
  def config = @config ||= Courrier.configuration.inbox
48
48
 
49
49
  def email_destination
50
- return "opened in your default browser" if config.auto_open
50
+ return "opened in your default browser" if Courrier.configuration.email[:auto_open]
51
51
 
52
52
  path = begin
53
53
  Rails.application.routes.url_helpers.courrier_path
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Courrier
4
+ class Email
5
+ module Translation
6
+ def t(key, **options)
7
+ key = "#{i18n_scope}#{key}" if key.start_with?(".")
8
+
9
+ I18n.t(key, **options)
10
+ end
11
+
12
+ private
13
+
14
+ def i18n_scope
15
+ "courrier.email.#{self.class.name.underscore.tr("/", ".")}"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -43,6 +43,7 @@ module Courrier
43
43
  end
44
44
  end
45
45
 
46
+ Courrier::Email.include Courrier::Email::Translation
46
47
  Courrier::Email.extend ClassMethods
47
48
  Courrier::Email.singleton_class.prepend InheritedHook
48
49
  end
@@ -4,8 +4,18 @@ module Courrier
4
4
 
5
5
  source_root File.expand_path("templates", __dir__)
6
6
 
7
+ class_option :provider, type: :string, desc: "Email delivery provider (#{Courrier::Email::Provider::PROVIDERS.keys.join(", ")})"
8
+
7
9
  def copy_initializer_file
8
10
  template "initializer.rb", "config/initializers/courrier.rb"
9
11
  end
12
+
13
+ private
14
+
15
+ def provider_config_options
16
+ provider_class = Courrier::Email::Provider::PROVIDERS[options[:provider]&.to_sym]
17
+
18
+ provider_class ? provider_class.config_options : []
19
+ end
10
20
  end
11
21
  end
@@ -2,19 +2,37 @@ Courrier.configure do |config|
2
2
  include Courrier::Email::Address
3
3
 
4
4
  # Set your email delivery provider
5
+ <% if options[:provider] -%>
6
+ config.email = {
7
+ provider: "<%= options[:provider] %>", # default: `logger`, other options: <%= Courrier::Email::Provider::PROVIDERS.keys.join(", ") %>
8
+ api_key: "" # provider API key
9
+ }
10
+ <% else -%>
5
11
  # config.email = {
6
- # provider: "", # default, `logger`, choose from: <%= Courrier::Email::Provider::PROVIDERS.keys.join(", ") %>
7
- # api_key: "" your transactional email provider's API key
12
+ # provider: "", # default, `logger`, other options: <%= Courrier::Email::Provider::PROVIDERS.keys.join(", ") %>
13
+ # api_key: "" # Your transactional email provider's API key
8
14
  # }
15
+ <% end -%>
9
16
 
17
+ <% if options[:provider] && provider_config_options.any? -%>
10
18
 
11
19
  # Configure provider-specific settings
12
- # config.providers.loops.transactional_id = ""
13
- # config.providers.mailgun.domain = ""
20
+ <% provider_config_options.each do |key| -%>
21
+ # config.providers.<%= options[:provider] %>.<%= key %> = ""
22
+ <% end -%>
23
+ <% elsif !options[:provider] -%>
24
+
25
+ # Configure provider-specific settings
26
+ <% Courrier::Email::Provider::PROVIDERS.each do |name, provider_class| -%>
27
+ <% provider_class.config_options.each do |key| -%>
28
+ # config.providers.<%= name %>.<%= key %> = ""
29
+ <% end -%>
30
+ <% end -%>
31
+ <% end -%>
14
32
 
15
33
 
16
34
  # Set default sender details
17
- config.from = email_with_name("support@example.com", "Example Support") # => `Example Support <support@example.com>`
35
+ config.from = email_with_name("support@example.com", "Example Support") # => `Example Support <support@example.com>`
18
36
  # config.reply_to = ""
19
37
  # config.cc = ""
20
38
  # config.bcc = ""
@@ -1,3 +1,3 @@
1
1
  module RailsCourrier
2
- VERSION = "0.5.0"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -12,6 +12,7 @@ require "launchy"
12
12
 
13
13
  require "courrier"
14
14
  require "courrier/engine"
15
+ require "courrier/email/translation"
15
16
  require "courrier/railtie"
16
17
  require "courrier/email/delivery_job"
17
18
  require "courrier/email/providers/inbox"
@@ -18,9 +18,9 @@ Gem::Specification.new do |spec|
18
18
 
19
19
  spec.files = Dir["{bin,app,config,lib}/**/*", "Rakefile", "README.md", "rails_courrier.gemspec", "Gemfile", "Gemfile.lock"]
20
20
 
21
- spec.required_ruby_version = ">= 4.0.0"
21
+ spec.required_ruby_version = ">= 3.4.0"
22
22
 
23
- spec.add_dependency "courrier", "~> 0.11.0"
23
+ spec.add_dependency "courrier", "~> 1.0.0"
24
24
  spec.add_dependency "launchy", ">= 3.1", "< 4"
25
25
  spec.add_dependency "railties", ">= 7.0"
26
26
  spec.add_dependency "actionpack", ">= 7.0"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_courrier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rails Designer
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 0.11.0
18
+ version: 1.0.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 0.11.0
25
+ version: 1.0.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: launchy
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -111,16 +111,19 @@ files:
111
111
  - Gemfile.lock
112
112
  - README.md
113
113
  - Rakefile
114
+ - app/assets/stylesheets/courrier/application.css
114
115
  - app/controllers/courrier/previews/cleanups_controller.rb
115
116
  - app/controllers/courrier/previews_controller.rb
117
+ - app/views/courrier/previews/default.html.erb
116
118
  - app/views/courrier/previews/index.html.erb
119
+ - bin/rails
117
120
  - bin/release
118
121
  - bin/setup
119
122
  - config/routes.rb
120
123
  - lib/courrier/configuration/inbox.rb
121
124
  - lib/courrier/email/delivery_job.rb
122
125
  - lib/courrier/email/providers/inbox.rb
123
- - lib/courrier/email/providers/inbox/default.html.erb
126
+ - lib/courrier/email/translation.rb
124
127
  - lib/courrier/engine.rb
125
128
  - lib/courrier/railtie.rb
126
129
  - lib/courrier/tasks/courrier.rake
@@ -146,14 +149,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
146
149
  requirements:
147
150
  - - ">="
148
151
  - !ruby/object:Gem::Version
149
- version: 4.0.0
152
+ version: 3.4.0
150
153
  required_rubygems_version: !ruby/object:Gem::Requirement
151
154
  requirements:
152
155
  - - ">="
153
156
  - !ruby/object:Gem::Version
154
157
  version: '0'
155
158
  requirements: []
156
- rubygems_version: 4.0.3
159
+ rubygems_version: 3.6.7
157
160
  specification_version: 4
158
161
  summary: Rails integration for Courrier email delivery
159
162
  test_files: []
@@ -1,127 +0,0 @@
1
- <!--
2
- {
3
- "to": "<%= email %>",
4
- "subject": "<%= @options.subject %>"
5
- }
6
- -->
7
-
8
- <!DOCTYPE html>
9
- <html>
10
- <head>
11
- <meta charset="UTF-8">
12
- <title><%= @options.subject %></title>
13
- <style>
14
- *, *::before, *::after { box-sizing: border-box; }
15
- * { margin: 0; padding: 0; }
16
- body { margin: 0; padding: 0; line-height: 1.5; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; background-color: rgb(241 245 249); }
17
- pre { margin: 0; white-space: pre-wrap; }
18
- .email {
19
- width: 100%;
20
- max-width: 65rem;
21
- margin-left: auto; margin-right: auto;
22
- background-color: #fff;
23
- box-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
24
- border-bottom-right-radius: 1rem; border-bottom-left-radius: 1rem;
25
- }
26
-
27
- .email__header { display: flex; align-items: center; padding: .75rem; -moz-column-gap: .75rem; column-gap: .75rem; }
28
-
29
- .email__avatar {
30
- flex-shrink: 0;
31
- padding: .25rem;
32
- width: 2rem; height: 2rem;
33
- background-color: rgb(203 213 225);
34
- color: rgb(51 65 85);
35
- border-radius: 9999px;
36
- }
37
-
38
- .email__recipient { flex-grow: 1; font-size: .875rem; line-height: 1.25rem; }
39
-
40
- .email__recipient-name { font-weight: 600; color: rgb(51 65 85); }
41
-
42
- .email__metadata { display: flex; align-items: center; justify-content: space-between; }
43
-
44
- .email__recipient-email { color: rgb(100 116 139); }
45
-
46
- .email__datetime { font-size: .75rem; line-height: 1rem; color: rgb(148 163 184); }
47
-
48
- .email__subject {
49
- margin-left: 3.5rem;
50
- font-size: 1.25rem; line-height: 1.75rem;
51
- font-weight: 700;
52
- letter-spacing: -.025em;
53
- color: rgb(30 41 59)
54
- }
55
-
56
- .email__preview { margin-top: .75rem; margin-left: 3.5rem; padding-bottom: 1rem; }
57
-
58
- .preview-toggle { display: none; }
59
- .preview-toggle-label {
60
- display: inline-block;
61
- padding: .25rem .875rem;
62
- font-size: .75rem; line-height: 1rem;
63
- font-weight: 600;
64
- color: rgb(30 41 59);
65
- background-color: rgb(255 255 255);
66
- border: 1px solid rgb(226 232 240);
67
- border-radius: .375rem;
68
- box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
69
- cursor: pointer;
70
-
71
- &:hover { border-color: rgb(203 213 225); }
72
- &:active { transform: scale(.98); }
73
- }
74
- .preview-toggle[disabled] + .preview-toggle-label { opacity: 0.5; cursor: not-allowed; }
75
-
76
- .email__preview-html, .email__preview-text { margin-top: 1.5rem;}
77
- .email__preview-html { display: none; }
78
- .preview-toggle:checked ~ .email__preview-text { display: none; }
79
- .preview-toggle:checked ~ .email__preview-html { display: block; }
80
- </style>
81
- </head>
82
-
83
- <body>
84
- <article class="email">
85
- <header class="email__header">
86
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon" class="email__avatar">
87
- <path fill-rule="evenodd" d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z" clip-rule="evenodd"/>
88
- </svg>
89
-
90
- <div class="email__recipient">
91
- <% if name %>
92
- <p class="email__recipient-name">
93
- <%= name %>
94
- </p>
95
- <% end %>
96
-
97
- <div class="email__metadata">
98
- <p class="email__recipient-email">
99
- <%= email %>
100
- </p>
101
-
102
- <time class="email__datetime" datetime="#{Time.now.strftime("%Y-%m-%d %H:%M:%S %z")}">
103
- <%= Time.now.strftime("%Y-%m-%d %H:%M:%S %z") %>
104
- </time>
105
- </div>
106
- </div>
107
- </header>
108
-
109
- <p class="email__subject">
110
- <%= @options.subject %>
111
- </p>
112
-
113
- <div class="email__preview">
114
- <input type="checkbox" id="preview-toggle" class="preview-toggle" <%= html ? "" : "disabled" %>>
115
- <label for="preview-toggle" class="preview-toggle-label">Toggle HTML/Text</label>
116
-
117
- <div class="email__preview-text">
118
- <pre><%= text || "(no text content)" %></pre>
119
- </div>
120
-
121
- <div class="email__preview-html">
122
- <%= html || "<p>(no HTML content)</p>" %>
123
- </div>
124
- </div>
125
- </article>
126
- </body>
127
- </html>