spree_emails 6.0.0.beta3 → 6.0.0.rc1

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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -74
  3. data/Rakefile +23 -0
  4. data/app/mailers/spree/company_mailer.rb +4 -7
  5. data/app/mailers/spree/customer_mailer.rb +6 -8
  6. data/app/mailers/spree/digital_asset_mailer.rb +18 -11
  7. data/app/mailers/spree/fulfillment_mailer.rb +11 -7
  8. data/app/mailers/spree/newsletter_mailer.rb +1 -2
  9. data/app/mailers/spree/order_group_mailer.rb +16 -20
  10. data/app/mailers/spree/order_mailer.rb +15 -20
  11. data/app/mailers/spree/return_mailer.rb +10 -5
  12. data/app/mailers/spree/seller_mailer.rb +8 -13
  13. data/app/models/spree/emails/amount_line.rb +32 -0
  14. data/app/serializers/spree/emails/address_serializer.rb +6 -0
  15. data/app/serializers/spree/emails/amount_line_serializer.rb +17 -0
  16. data/app/serializers/spree/emails/applied_promotion_serializer.rb +6 -0
  17. data/app/serializers/spree/emails/delivery_method_serializer.rb +6 -0
  18. data/app/serializers/spree/emails/delivery_rate_serializer.rb +10 -0
  19. data/app/serializers/spree/emails/delivery_serializer.rb +6 -0
  20. data/app/serializers/spree/emails/fee_serializer.rb +6 -0
  21. data/app/serializers/spree/emails/freight_summary_serializer.rb +6 -0
  22. data/app/serializers/spree/emails/fulfillment_group_serializer.rb +22 -0
  23. data/app/serializers/spree/emails/fulfillment_serializer.rb +19 -0
  24. data/app/serializers/spree/emails/gift_card_serializer.rb +6 -0
  25. data/app/serializers/spree/emails/line_item_serializer.rb +22 -0
  26. data/app/serializers/spree/emails/market_serializer.rb +6 -0
  27. data/app/serializers/spree/emails/option_value_serializer.rb +6 -0
  28. data/app/serializers/spree/emails/order_group_serializer.rb +60 -0
  29. data/app/serializers/spree/emails/order_serializer.rb +25 -0
  30. data/app/serializers/spree/emails/parcel_item_serializer.rb +36 -0
  31. data/app/serializers/spree/emails/payment_method_serializer.rb +6 -0
  32. data/app/serializers/spree/emails/payment_serializer.rb +9 -0
  33. data/app/serializers/spree/emails/purchase_summary_attributes.rb +41 -0
  34. data/app/serializers/spree/emails/purchased_item_attributes.rb +28 -0
  35. data/app/serializers/spree/emails/return_serializer.rb +17 -0
  36. data/app/serializers/spree/emails/stock_location_serializer.rb +6 -0
  37. data/app/services/spree/emails/samples/base.rb +79 -0
  38. data/app/services/spree/emails/samples/company_invitation.rb +19 -0
  39. data/app/services/spree/emails/samples/data_export.rb +11 -0
  40. data/app/services/spree/emails/samples/downloads.rb +28 -0
  41. data/app/services/spree/emails/samples/fulfillment.rb +33 -0
  42. data/app/services/spree/emails/samples/newsletter_confirmation.rb +11 -0
  43. data/app/services/spree/emails/samples/order.rb +22 -0
  44. data/app/services/spree/emails/samples/order_group.rb +22 -0
  45. data/app/services/spree/emails/samples/password_reset.rb +15 -0
  46. data/app/services/spree/emails/samples/payment_link.rb +17 -0
  47. data/app/services/spree/emails/samples/return.rb +29 -0
  48. data/app/subscribers/spree/order_email_subscriber.rb +2 -2
  49. data/app/subscribers/spree/order_group_email_subscriber.rb +1 -1
  50. data/app/subscribers/spree/return_email_subscriber.rb +2 -0
  51. data/app/views/spree/company_mailer/invitation_email.liquid +13 -0
  52. data/app/views/spree/customer_mailer/data_export_email.liquid +18 -0
  53. data/app/views/spree/customer_mailer/password_reset_email.liquid +14 -0
  54. data/app/views/spree/digital_asset_mailer/files_ready_email.liquid +23 -0
  55. data/app/views/spree/fulfillment_mailer/fulfilled_email.liquid +21 -0
  56. data/app/views/spree/newsletter_mailer/email_confirmation.liquid +13 -0
  57. data/app/views/spree/order_group_mailer/confirm_email.liquid +35 -0
  58. data/app/views/spree/order_group_mailer/store_owner_notification_email.liquid +11 -0
  59. data/app/views/spree/order_mailer/cancel_email.liquid +12 -0
  60. data/app/views/spree/order_mailer/confirm_email.liquid +12 -0
  61. data/app/views/spree/order_mailer/payment_link_email.liquid +12 -0
  62. data/app/views/spree/order_mailer/store_owner_notification_email.liquid +11 -0
  63. data/app/views/spree/return_mailer/refunded_email.liquid +18 -0
  64. data/app/views/spree/seller_mailer/approved_email.liquid +13 -0
  65. data/app/views/spree/seller_mailer/rejected_email.liquid +11 -0
  66. data/app/views/spree/seller_mailer/suspended_email.liquid +11 -0
  67. data/app/views/spree/shared/_fulfillment_group.liquid +20 -0
  68. data/app/views/spree/shared/_line_item.liquid +29 -0
  69. data/app/views/spree/shared/_order_summary.liquid +17 -0
  70. data/app/views/spree/shared/_purchase_totals.liquid +28 -0
  71. data/config/initializers/typelizer.rb +60 -0
  72. data/config/locales/ar.yml +40 -0
  73. data/config/locales/be.yml +37 -0
  74. data/config/locales/bg.yml +31 -0
  75. data/config/locales/ca.yml +35 -0
  76. data/config/locales/cs.yml +38 -0
  77. data/config/locales/da.yml +37 -0
  78. data/config/locales/de-CH.yml +11 -0
  79. data/config/locales/de.yml +38 -0
  80. data/config/locales/el.yml +37 -0
  81. data/config/locales/en.yml +0 -25
  82. data/config/locales/es-CL.yml +21 -0
  83. data/config/locales/es-EC.yml +22 -0
  84. data/config/locales/es-MX.yml +26 -0
  85. data/config/locales/es.yml +40 -0
  86. data/config/locales/et.yml +31 -0
  87. data/config/locales/fa.yml +37 -0
  88. data/config/locales/fi.yml +37 -0
  89. data/config/locales/fr.yml +38 -0
  90. data/config/locales/hu.yml +37 -0
  91. data/config/locales/id.yml +37 -0
  92. data/config/locales/it.yml +38 -0
  93. data/config/locales/ja.yml +36 -0
  94. data/config/locales/km.yml +37 -0
  95. data/config/locales/ko.yml +36 -0
  96. data/config/locales/ku.yml +37 -0
  97. data/config/locales/lt.yml +37 -0
  98. data/config/locales/lv.yml +39 -0
  99. data/config/locales/nb.yml +37 -0
  100. data/config/locales/ne.yml +37 -0
  101. data/config/locales/nl.yml +38 -0
  102. data/config/locales/pl.yml +39 -0
  103. data/config/locales/pt-BR.yml +34 -0
  104. data/config/locales/pt.yml +34 -0
  105. data/config/locales/ro.yml +37 -0
  106. data/config/locales/ru.yml +38 -0
  107. data/config/locales/sk.yml +31 -0
  108. data/config/locales/sl-SI.yml +31 -0
  109. data/config/locales/sv.yml +37 -0
  110. data/config/locales/th.yml +37 -0
  111. data/config/locales/tr.yml +38 -0
  112. data/config/locales/uk.yml +42 -0
  113. data/config/locales/vi.yml +35 -0
  114. data/config/locales/zh-CN.yml +38 -0
  115. data/config/locales/zh-TW.yml +40 -0
  116. data/lib/spree/emails/engine.rb +27 -3
  117. data/lib/spree/emails/previews/company_preview.rb +10 -0
  118. data/lib/spree/emails/previews/customer_preview.rb +4 -0
  119. data/lib/spree/emails/previews/digital_asset_preview.rb +20 -0
  120. metadata +107 -55
  121. data/app/helpers/spree/base_helper.rb +0 -45
  122. data/app/helpers/spree/digital_asset_helper.rb +0 -16
  123. data/app/helpers/spree/fulfillment_helper.rb +0 -12
  124. data/app/helpers/spree/mail_helper.rb +0 -10
  125. data/app/views/spree/company_mailer/invitation_email.html.erb +0 -8
  126. data/app/views/spree/company_mailer/invitation_email.text.erb +0 -11
  127. data/app/views/spree/customer_mailer/data_export_email.html.erb +0 -12
  128. data/app/views/spree/customer_mailer/data_export_email.text.erb +0 -9
  129. data/app/views/spree/customer_mailer/password_reset_email.html.erb +0 -10
  130. data/app/views/spree/customer_mailer/password_reset_email.text.erb +0 -13
  131. data/app/views/spree/digital_asset_mailer/files_ready_email.html.erb +0 -20
  132. data/app/views/spree/digital_asset_mailer/files_ready_email.text.erb +0 -10
  133. data/app/views/spree/fulfillment_mailer/fulfilled_email.html.erb +0 -22
  134. data/app/views/spree/fulfillment_mailer/fulfilled_email.text.erb +0 -18
  135. data/app/views/spree/newsletter_mailer/email_confirmation.html.erb +0 -12
  136. data/app/views/spree/newsletter_mailer/email_confirmation.text.erb +0 -9
  137. data/app/views/spree/order_group_mailer/confirm_email.html.erb +0 -38
  138. data/app/views/spree/order_group_mailer/confirm_email.text.erb +0 -26
  139. data/app/views/spree/order_group_mailer/store_owner_notification_email.html.erb +0 -14
  140. data/app/views/spree/order_group_mailer/store_owner_notification_email.text.erb +0 -11
  141. data/app/views/spree/order_mailer/cancel_email.html.erb +0 -12
  142. data/app/views/spree/order_mailer/cancel_email.text.erb +0 -13
  143. data/app/views/spree/order_mailer/confirm_email.html.erb +0 -12
  144. data/app/views/spree/order_mailer/confirm_email.text.erb +0 -13
  145. data/app/views/spree/order_mailer/payment_link_email.html.erb +0 -11
  146. data/app/views/spree/order_mailer/store_owner_notification_email.html.erb +0 -11
  147. data/app/views/spree/order_mailer/store_owner_notification_email.text.erb +0 -13
  148. data/app/views/spree/return_mailer/refunded_email.html.erb +0 -22
  149. data/app/views/spree/return_mailer/refunded_email.text.erb +0 -20
  150. data/app/views/spree/seller_mailer/approved_email.html.erb +0 -8
  151. data/app/views/spree/seller_mailer/approved_email.text.erb +0 -8
  152. data/app/views/spree/seller_mailer/rejected_email.html.erb +0 -6
  153. data/app/views/spree/seller_mailer/rejected_email.text.erb +0 -7
  154. data/app/views/spree/seller_mailer/suspended_email.html.erb +0 -6
  155. data/app/views/spree/seller_mailer/suspended_email.text.erb +0 -7
  156. data/app/views/spree/shared/_fulfillment_group_section.html.erb +0 -37
  157. data/app/views/spree/shared/_fulfillment_group_section.text.erb +0 -12
  158. data/app/views/spree/shared/_item_thumbnail_link.html.erb +0 -18
  159. data/app/views/spree/shared/_order_summary_section.html.erb +0 -20
  160. data/app/views/spree/shared/_po_number.text.erb +0 -6
  161. data/app/views/spree/shared/_purchase_totals.html.erb +0 -62
  162. data/app/views/spree/shared/_purchase_totals.text.erb +0 -24
  163. data/app/views/spree/shared/_purchased_items_table.html.erb +0 -41
  164. data/app/views/spree/shared/_purchased_items_table.text.erb +0 -28
  165. data/app/views/spree/shared/purchased_items_table/_line_item.html.erb +0 -24
  166. data/app/views/spree/shared/purchased_items_table/_subtotal.html.erb +0 -3
  167. data/app/views/spree/shared/purchased_items_table/_summary_row.html.erb +0 -14
  168. data/app/views/spree/shared/purchased_items_table/_total.html.erb +0 -4
  169. data/config/i18n-tasks.yml +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e39192d17144a4963b3c0fb926d656cc639f165927b8fa78956aa230c58bfc6
4
- data.tar.gz: '0878e0210c83640d6c35b063c2e7dc89f427a6e58f178838b562dadc8bd2ac20'
3
+ metadata.gz: 460c53ff1f08bac66ed9a76caadbdedf216833d3faa0bf2dd7b2e6331f72a6a1
4
+ data.tar.gz: 73c0209ebcd6c95e34f61d27c6d4b808a3b86451f4c2f7b3b9ca995f65418e71
5
5
  SHA512:
6
- metadata.gz: 24d2af93481b95787c95ff9b442b7a723e1564fbecf89cf87b61320cd9240f172111b0be599ab12f1d334df99594702af1ee7ab405ab02ae6f2a438f31103546
7
- data.tar.gz: c686e22de2d14aa715ee81593c649333fff0c2f2bfdf45336fd0b3a6e125244be88cbc892e168f130156baefdb8dec06bf4fe382d5ae8e93b9ba45901c530f73
6
+ metadata.gz: 6ced0f9e3ef9421db913ec441cdc8b90cd2bc731c2cab7538e99bc29e358f0565903ed7a13cf11ca8931438c6315dce67479b061ee20f1e168f2e4eab2d8ec62
7
+ data.tar.gz: 455ae9a4a57332fa838eeedec373640d356fdbf7ec5f623cd249054bfdca31f91318d1fde93338307da3de5ed270613725e8070adc5d17e4122117554518efd2
data/README.md CHANGED
@@ -8,11 +8,11 @@ Spree Emails provides transactional email templates and mailers for Spree Commer
8
8
 
9
9
  This gem includes:
10
10
 
11
- - **Order Mailer** - Order confirmation and cancellation emails
12
- - **Shipment Mailer** - Shipping and delivery notifications
13
- - **Reimbursement Mailer** - Refund notifications
14
- - **Event Subscribers** - Automatic email triggers on store events
15
- - **Email Templates** - Customizable HTML and text templates
11
+ - **Mailers** for order confirmation, cancellation and payment links, multi-seller purchase confirmations, fulfillment notices, refunds, digital downloads, customer password resets and data exports, newsletter confirmation, company invitations and seller status changes
12
+ - **Event subscribers** that send them when store events happen
13
+ - **Liquid templates** written in MJML, one per email, that you can override
14
+
15
+ Staff emails (password resets, invitations, import and export results, disabled webhooks) live in `spree_core` and render the same way.
16
16
 
17
17
  ## Installation
18
18
 
@@ -20,22 +20,6 @@ This gem includes:
20
20
  bundle add spree_emails
21
21
  ```
22
22
 
23
- ## Email Types
24
-
25
- ### Order Emails
26
-
27
- - **Order Confirmation** - Sent when an order is completed
28
- - **Order Cancellation** - Sent when an order is cancelled
29
-
30
- ### Shipment Emails
31
-
32
- - **Shipment Notification** - Sent when a shipment is shipped
33
- - **Delivery Confirmation** - Sent when tracking shows delivered
34
-
35
- ### Reimbursement Emails
36
-
37
- - **Refund Notification** - Sent when a reimbursement is processed
38
-
39
23
  ## Configuration
40
24
 
41
25
  Transactional emails are controlled per-store via the `send_consumer_transactional_emails` preference. This can be configured in the admin dashboard under Store Settings, or programmatically:
@@ -69,72 +53,44 @@ config.action_mailer.smtp_settings = {
69
53
 
70
54
  ## Customization
71
55
 
72
- ### Overriding Templates
73
-
74
- Copy email templates to your application:
56
+ ### Overriding templates
75
57
 
76
- ```bash
77
- # Copy all email templates
78
- cp -r $(bundle show spree_emails)/app/views/spree/mailer app/views/spree/
58
+ Every email renders from a Liquid template at its Rails view path. To change one, create the file at the same path in your app — for example `app/views/spree/order_mailer/confirm_email.liquid` — starting from the copy in this gem's `app/views`. The layout around every email is `app/views/layouts/spree/base_mailer.liquid`.
79
59
 
80
- # Or copy specific templates
81
- cp $(bundle show spree_emails)/app/views/spree/mailer/order_mailer/confirm_email.html.erb \
82
- app/views/spree/mailer/order_mailer/
83
- ```
60
+ Templates read plain data from serializers, never models: see the [email templates guide](https://spreecommerce.org/docs/developer/customization/emails) for the layout, partials, filters and escaping, and the [variable reference](https://spreecommerce.org/docs/developer/customization/email-variables) for what each template receives.
84
61
 
85
- ### Template Structure
86
-
87
- ```
88
- app/views/spree/mailer/
89
- ├── order_mailer/
90
- │ ├── confirm_email.html.erb
91
- │ ├── confirm_email.text.erb
92
- │ ├── cancel_email.html.erb
93
- │ └── cancel_email.text.erb
94
- ├── shipment_mailer/
95
- │ ├── shipped_email.html.erb
96
- │ └── shipped_email.text.erb
97
- └── reimbursement_mailer/
98
- ├── reimbursement_email.html.erb
99
- └── reimbursement_email.text.erb
100
- ```
62
+ Mailers of your own that render ERB views keep working, wrapped in the same layout.
101
63
 
102
- ### Custom Mailer
64
+ ### Adding new email types
103
65
 
104
- Create custom mailers by extending Spree's base mailer:
66
+ Extend Spree's base mailer and render a Liquid template at the action's view path, passing the data it needs:
105
67
 
106
68
  ```ruby
107
- # app/mailers/spree/order_mailer_decorator.rb
69
+ # app/mailers/spree/custom_mailer.rb
108
70
  module Spree
109
- module OrderMailerDecorator
110
- def confirm_email(order, resend = false)
111
- @custom_data = fetch_custom_data(order)
112
- super
113
- end
114
-
115
- private
71
+ class CustomMailer < BaseMailer
72
+ def welcome_email(customer, store)
73
+ @current_store = store
116
74
 
117
- def fetch_custom_data(order)
118
- # Custom logic
75
+ with_store_locale(@current_store) do
76
+ mail_template({ customer: email_data(customer, Spree.api.customer_serializer) }, to: customer.email)
77
+ end
119
78
  end
120
79
  end
121
80
  end
122
-
123
- Spree::OrderMailer.prepend(Spree::OrderMailerDecorator)
124
81
  ```
125
82
 
126
- ### Adding New Email Types
127
-
128
- ```ruby
129
- # app/mailers/spree/custom_mailer.rb
130
- module Spree
131
- class CustomMailer < BaseMailer
132
- def welcome_email(user)
133
- @user = user
134
- mail(to: @user.email, subject: 'Welcome to our store!')
135
- end
136
- end
137
- end
83
+ And its template, `app/views/spree/custom_mailer/welcome_email.liquid` (the front matter must open the file):
84
+
85
+ ```liquid
86
+ ---
87
+ subject: "Welcome to {{ store.name }}"
88
+ ---
89
+ <mj-section>
90
+ <mj-column css-class="hero">
91
+ <mj-text mj-class="heading">Welcome, {{ customer.first_name }}!</mj-text>
92
+ </mj-column>
93
+ </mj-section>
138
94
  ```
139
95
 
140
96
  ## Event Integration
@@ -152,7 +108,7 @@ module MyApp
152
108
  user = Spree.customer_class.find_by(id: user_id)
153
109
  return unless user
154
110
 
155
- Spree::CustomMailer.welcome_email(user).deliver_later
111
+ Spree::CustomMailer.welcome_email(user, Spree::Store.default).deliver_later
156
112
  end
157
113
  end
158
114
  end
data/Rakefile CHANGED
@@ -13,3 +13,26 @@ task :test_app do
13
13
  ENV['LIB_NAME'] = 'spree/emails'
14
14
  Rake::Task['common:test_app'].invoke
15
15
  end
16
+
17
+ namespace :typelizer do
18
+ desc 'Generate TypeScript types for every SDK, the email template types included'
19
+ task :generate do
20
+ ENV['RAILS_ENV'] ||= 'test'
21
+ ENV['DISABLE_TYPELIZER'] = 'false'
22
+ ENV['ENABLE_TYPELIZER'] = '1'
23
+
24
+ dummy = File.expand_path('spec/dummy/config/environment.rb', __dir__)
25
+ abort 'Build the test app first: cd spree/emails && bundle exec rake test_app' unless File.exist?(dummy)
26
+
27
+ require dummy
28
+ require 'spree/api/typelizer_generation'
29
+
30
+ Spree::Api::TypelizerGeneration.call([
31
+ Spree::Api::Engine.root.join('app/serializers/spree/api/v3'),
32
+ Spree::Emails::Engine.root.join('app/serializers/spree/emails'),
33
+ Spree::Core::Engine.root.join('app/serializers/spree/emails')
34
+ ])
35
+
36
+ puts 'Generated types → packages/sdk, packages/admin-sdk and packages/seller-sdk'
37
+ end
38
+ end
@@ -4,8 +4,6 @@ module Spree
4
4
  # invitation, this is transactional, not marketing: the no-welcome-email
5
5
  # rule on customer creation is untouched.
6
6
  class CompanyMailer < BaseMailer
7
- helper Spree::MailHelper
8
-
9
7
  # The invite carries the plaintext token; the storefront owns the
10
8
  # acceptance page, so the link resolves against the store URL like other
11
9
  # customer-facing links.
@@ -19,14 +17,13 @@ module Spree
19
17
  return message.perform_deliveries = false unless @invitation.reload.pending?
20
18
 
21
19
  @company = @invitation.company
22
- store = @company.store
20
+ store = @current_store = @company.store
23
21
  @accept_url = append_token(invitation_base_url(store), @invitation.token)
24
22
 
25
23
  with_store_locale(store) do
26
- mail(
27
- to: @invitation.email,
28
- subject: "#{store.name} #{Spree.t('company_mailer.invitation_email.subject', company: @company.name)}",
29
- store_url: store.storefront_url
24
+ mail_template(
25
+ { company: email_data(@company, Spree.api.company_serializer), accept_url: @accept_url },
26
+ to: @invitation.email, store_url: store.storefront_url
30
27
  )
31
28
  end
32
29
  end
@@ -10,10 +10,9 @@ module Spree
10
10
  @reset_url = append_token(base_url, reset_token)
11
11
 
12
12
  with_store_locale(store) do
13
- mail(
14
- to: user.email,
15
- subject: "#{store.name} #{Spree.t('customer_mailer.password_reset_email.subject')}",
16
- store_url: store.storefront_url
13
+ mail_template(
14
+ { customer: email_data(user, Spree.api.customer_serializer), reset_url: @reset_url },
15
+ to: user.email, store_url: store.storefront_url
17
16
  )
18
17
  end
19
18
  end
@@ -31,10 +30,9 @@ module Spree
31
30
  @expires_at = data_request.expires_at
32
31
 
33
32
  with_store_locale(@current_store) do
34
- mail(
35
- to: data_request.email,
36
- subject: "#{@current_store.name} #{Spree.t('customer_mailer.data_export_email.subject')}",
37
- store_url: @current_store.storefront_url
33
+ mail_template(
34
+ { download_url: @download_url, expires_at: @expires_at&.iso8601 },
35
+ to: data_request.email, store_url: @current_store.storefront_url
38
36
  )
39
37
  end
40
38
  end
@@ -1,8 +1,5 @@
1
1
  module Spree
2
2
  class DigitalAssetMailer < BaseMailer
3
- helper Spree::MailHelper
4
- helper Spree::DigitalAssetHelper
5
-
6
3
  # Sent once an order's downloads are ready. Kept separate from the order
7
4
  # confirmation so it can be re-sent on its own, and so a storefront that
8
5
  # replaces the confirmation email does not silently lose the links.
@@ -13,17 +10,27 @@ module Spree
13
10
  @digital_links = @order.digital_links.includes(digital_asset: { attachment_attachment: :blob })
14
11
  return if @digital_links.empty?
15
12
 
16
- current_store = @order.store
17
13
  @download_host = current_store.formatted_url
18
14
  with_store_locale(current_store, @order.locale) do
19
- subject = order_email_subject(
20
- current_store,
21
- Spree.t('digital_asset_mailer.files_ready_email.subject'),
22
- @order.number,
23
- resend: resend
15
+ mail_template(
16
+ { order: email_data(@order, Spree::Emails::OrderSerializer),
17
+ downloads: downloads, resend: resend },
18
+ to: @order.email, store_url: current_store.storefront_url
24
19
  )
25
- mail(to: @order.email, subject: subject, store_url: current_store.storefront_url,
26
- template_path: 'spree/digital_asset_mailer', template_name: 'files_ready_email')
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ # Download URLs carry each link's bearer token, so they are built here
26
+ # rather than serialized. They point at the backend, not the storefront.
27
+ def downloads
28
+ @digital_links.map do |digital_link|
29
+ {
30
+ filename: digital_link.filename.to_s,
31
+ url: Spree::Api::DigitalLinkUrls.download_url(digital_link, @download_host),
32
+ expires_at: digital_link.expires_at&.iso8601
33
+ }
27
34
  end
28
35
  end
29
36
  end
@@ -1,16 +1,20 @@
1
1
  module Spree
2
2
  class FulfillmentMailer < BaseMailer
3
- helper Spree::MailHelper
4
- helper Spree::FulfillmentHelper
5
-
6
3
  def fulfilled_email(fulfillment, resend = false)
7
4
  @fulfillment = fulfillment.respond_to?(:id) ? fulfillment : Spree::Fulfillment.find(fulfillment)
8
5
  @order = @fulfillment.order
9
- current_store = @fulfillment.store
6
+ @current_store = @fulfillment.store
7
+
10
8
  with_store_locale(current_store, @order.locale) do
11
- subject = order_email_subject(current_store, Spree.t('fulfillment_mailer.fulfilled_email.subject'), @order.number, resend: resend)
12
- mail(to: @order.email, subject: subject, store_url: current_store.storefront_url,
13
- template_path: 'spree/fulfillment_mailer', template_name: 'fulfilled_email')
9
+ mail_template(
10
+ {
11
+ fulfillment: email_data(@fulfillment, Spree::Emails::FulfillmentSerializer),
12
+ order: email_data(@order, Spree::Emails::OrderSerializer),
13
+ resend: resend
14
+ },
15
+ template: 'spree/fulfillment_mailer/fulfilled_email',
16
+ to: @order.email, store_url: current_store.storefront_url
17
+ )
14
18
  end
15
19
  end
16
20
  end
@@ -9,9 +9,8 @@ module Spree
9
9
  base_url = redirect_url.presence || store.storefront_url
10
10
  @confirm_email_url = append_token(base_url, @subscriber.verification_token)
11
11
  with_store_locale(store) do
12
- mail(to: @subscriber.email, subject: Spree.t('newsletter_mailer.email_confirmation.subject'))
12
+ mail_template({ confirmation_url: @confirm_email_url }, to: @subscriber.email)
13
13
  end
14
14
  end
15
-
16
15
  end
17
16
  end
@@ -12,34 +12,16 @@ module Spree
12
12
  # email unchanged, and a storefront replacing one document should not have to
13
13
  # inherit the other.
14
14
  class OrderGroupMailer < BaseMailer
15
- helper Spree::MailHelper
16
-
17
15
  # @param order_group [Spree::OrderGroup, Integer, String] record or id
18
16
  # @param resend [Boolean] prefixes the subject, for an admin re-send
19
17
  def confirm_email(order_group, resend = false)
20
18
  @order_group = find_order_group(order_group)
21
- # Assigned rather than left to BaseMailer#current_store, which memoizes
22
- # off @order and would otherwise fall back to the default store — the
23
- # wrong store's name, logo and footer on a multi-store install.
24
- @current_store = @order_group.store
25
-
26
- with_store_locale(@current_store, @order_group.locale) do
27
- subject = order_email_subject(
28
- @current_store, Spree.t('order_group_mailer.confirm_email.subject'), @order_group.number, resend: resend
29
- )
30
- mail(to: @order_group.email, subject: subject, store_url: @current_store.storefront_url)
31
- end
19
+ deliver_order_group_email(to: @order_group.email, locale: @order_group.locale, resend: resend)
32
20
  end
33
21
 
34
22
  def store_owner_notification_email(order_group)
35
23
  @order_group = find_order_group(order_group)
36
- @current_store = @order_group.store
37
-
38
- with_store_locale(@current_store) do
39
- subject = Spree.t('order_group_mailer.store_owner_notification_email.subject', store_name: @current_store.name)
40
- mail(to: @current_store.new_order_notifications_email, subject: subject,
41
- store_url: @current_store.storefront_url)
42
- end
24
+ deliver_order_group_email(to: @order_group.store.new_order_notifications_email)
43
25
  end
44
26
 
45
27
  private
@@ -47,5 +29,19 @@ module Spree
47
29
  def find_order_group(order_group)
48
30
  order_group.respond_to?(:id) ? order_group : Spree::OrderGroup.find(order_group)
49
31
  end
32
+
33
+ def deliver_order_group_email(to:, locale: nil, resend: false)
34
+ # Assigned rather than left to BaseMailer#current_store, which memoizes
35
+ # off @order and would otherwise fall back to the default store — the
36
+ # wrong store's name, logo and footer on a multi-store install.
37
+ @current_store = @order_group.store
38
+
39
+ with_store_locale(@current_store, locale) do
40
+ mail_template(
41
+ { order_group: email_data(@order_group, Spree::Emails::OrderGroupSerializer), resend: resend },
42
+ to: to, store_url: @current_store.storefront_url
43
+ )
44
+ end
45
+ end
50
46
  end
51
47
  end
@@ -1,42 +1,37 @@
1
1
  module Spree
2
2
  class OrderMailer < BaseMailer
3
- helper Spree::MailHelper
4
-
5
3
  def confirm_email(order, resend = false)
6
4
  @order = order.respond_to?(:id) ? order : Spree::Order.find(order)
7
- current_store = @order.store
8
- with_store_locale(current_store, @order.locale) do
9
- subject = order_email_subject(current_store, Spree.t('order_mailer.confirm_email.subject'), @order.number, resend: resend)
10
- mail(to: @order.email, subject: subject, store_url: current_store.storefront_url)
11
- end
5
+ deliver_order_email(to: @order.email, locale: @order.locale, resend: resend)
12
6
  end
13
7
 
14
8
  def store_owner_notification_email(order)
15
9
  @order = order.respond_to?(:id) ? order : Spree::Order.find(order)
16
- current_store = @order.store
17
- with_store_locale(current_store) do
18
- subject = Spree.t('order_mailer.store_owner_notification_email.subject', store_name: current_store.name)
19
- mail(to: current_store.new_order_notifications_email, subject: subject, store_url: current_store.storefront_url)
20
- end
10
+ deliver_order_email(to: current_store.new_order_notifications_email)
21
11
  end
22
12
 
23
13
  def cancel_email(order, resend = false)
24
14
  @order = order.respond_to?(:id) ? order : Spree::Order.find(order)
25
- current_store = @order.store
26
- with_store_locale(current_store, @order.locale) do
27
- subject = order_email_subject(current_store, Spree.t('order_mailer.cancel_email.subject'), @order.number, resend: resend)
28
- mail(to: @order.email, subject: subject, store_url: current_store.storefront_url)
29
- end
15
+ deliver_order_email(to: @order.email, locale: @order.locale, resend: resend)
30
16
  end
31
17
 
32
18
  def payment_link_email(order_id)
33
19
  @order = Spree::Order.incomplete.not_canceled.find(order_id)
34
20
  @current_store = @order.store
21
+ # Carries the order token, so it is built here and never serialized.
35
22
  @checkout_payment_url = URI.join(@current_store.storefront_url, "/checkout/#{@order.token}/payment").to_s
36
23
 
37
- with_store_locale(@current_store, @order.locale) do
38
- mail(to: @order.email, subject: Spree.t('order_mailer.payment_link_email.subject', number: @order.number),
39
- store_url: @current_store.storefront_url)
24
+ deliver_order_email(to: @order.email, locale: @order.locale, payment_url: @checkout_payment_url)
25
+ end
26
+
27
+ private
28
+
29
+ def deliver_order_email(to:, locale: nil, **assigns)
30
+ with_store_locale(current_store, locale) do
31
+ mail_template(
32
+ { order: email_data(@order, Spree::Emails::OrderSerializer), resend: false }.merge(assigns),
33
+ to: to, store_url: current_store.storefront_url
34
+ )
40
35
  end
41
36
  end
42
37
  end
@@ -1,16 +1,21 @@
1
1
  module Spree
2
2
  class ReturnMailer < BaseMailer
3
- helper Spree::MailHelper
4
-
5
3
  # Tells the customer their returned items were refunded. Replaces the
6
4
  # reimbursement email dropped with the ReturnAuthorization chain in 6.0.
7
5
  def refunded_email(return_record, resend = false)
8
6
  @return = return_record.respond_to?(:id) ? return_record : Spree::Return.find(return_record)
9
7
  @order = @return.order
10
- current_store = @return.store || Spree::Store.default
8
+ @current_store = @return.store || Spree::Store.default
9
+
11
10
  with_store_locale(current_store, @order.locale) do
12
- subject = order_email_subject(current_store, Spree.t('return_mailer.refunded_email.subject'), @order.number, resend: resend)
13
- mail(to: @order.email, subject: subject, store_url: current_store.storefront_url)
11
+ mail_template(
12
+ {
13
+ return: email_data(@return, Spree::Emails::ReturnSerializer),
14
+ order: email_data(@order, Spree::Emails::OrderSerializer),
15
+ resend: resend
16
+ },
17
+ to: @order.email, store_url: current_store.storefront_url
18
+ )
14
19
  end
15
20
  end
16
21
  end
@@ -6,8 +6,6 @@ module Spree
6
6
  # is a lot of mail about something staff can already see in the dashboard.
7
7
  # Marketplace-side notifications ride the event and webhook surface instead.
8
8
  class SellerMailer < BaseMailer
9
- helper Spree::MailHelper
10
-
11
9
  # @param seller [Spree::Seller, Integer]
12
10
  def approved_email(seller)
13
11
  @seller = load_seller(seller)
@@ -15,7 +13,7 @@ module Spree
15
13
  # so a hosted dashboard, a dev Vite server and a mounted build all work.
16
14
  @dashboard_url = Spree::Stores::DashboardUrl.call(store: store).presence
17
15
 
18
- deliver_to_seller('seller_mailer.approved_email.subject')
16
+ deliver_to_seller
19
17
  end
20
18
 
21
19
  # Carries no reason: the operator's note is an internal record, and a
@@ -23,13 +21,13 @@ module Spree
23
21
  def suspended_email(seller)
24
22
  @seller = load_seller(seller)
25
23
 
26
- deliver_to_seller('seller_mailer.suspended_email.subject')
24
+ deliver_to_seller
27
25
  end
28
26
 
29
27
  def rejected_email(seller)
30
28
  @seller = load_seller(seller)
31
29
 
32
- deliver_to_seller('seller_mailer.rejected_email.subject')
30
+ deliver_to_seller
33
31
  end
34
32
 
35
33
  private
@@ -49,18 +47,15 @@ module Spree
49
47
  (@seller.users.pluck(:email) << @seller.contact_email).compact_blank.uniq(&:downcase)
50
48
  end
51
49
 
52
- # Takes the key, not the translated string: resolving the subject outside
53
- # the block would render it in whatever locale the job happens to run
54
- # under, so a seller could get an English subject over a German email.
55
- def deliver_to_seller(subject_key)
50
+ def deliver_to_seller
56
51
  addresses = recipients
57
52
  return message.perform_deliveries = false if addresses.empty?
58
53
 
54
+ @current_store = store
59
55
  with_store_locale(store) do
60
- mail(
61
- to: addresses,
62
- subject: Spree.t(subject_key, store_name: store.name),
63
- store_url: store.storefront_url
56
+ mail_template(
57
+ { seller: email_data(@seller, Spree.api.seller_serializer), dashboard_url: @dashboard_url },
58
+ to: addresses, store_url: store.storefront_url
64
59
  )
65
60
  end
66
61
  end
@@ -0,0 +1,32 @@
1
+ module Spree
2
+ module Emails
3
+ # One labelled amount on an email's summary: a promotion, a manual
4
+ # discount, a delivery rate or a fee, with rows sharing a label summed.
5
+ class AmountLine
6
+ include ActiveModel::Model
7
+ include ActiveModel::Attributes
8
+ extend Spree::DisplayMoney
9
+
10
+ attribute :label, :string
11
+ attribute :amount, :decimal
12
+ attribute :currency, :string
13
+
14
+ money_methods :amount
15
+
16
+ # Sums records sharing a label into one line each.
17
+ #
18
+ # @param records [Enumerable] the rows to sum
19
+ # @param currency [String]
20
+ # @param label [Symbol, Proc] names each row
21
+ # @param amount [Symbol] the method giving each row's amount
22
+ # @param keep_zero [Boolean] whether a line coming to zero is kept
23
+ # @return [Array<Spree::Emails::AmountLine>]
24
+ def self.group(records, currency:, label: :label, amount: :amount, keep_zero: false)
25
+ records.to_a.group_by(&label).filter_map do |name, rows|
26
+ sum = rows.sum { |row| row.public_send(amount).to_d }
27
+ new(label: name, amount: sum, currency: currency) if keep_zero || !sum.zero?
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,6 @@
1
+ module Spree
2
+ module Emails
3
+ class AddressSerializer < Spree::Api::V3::AddressSerializer
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,17 @@
1
+ module Spree
2
+ module Emails
3
+ class AmountLineSerializer
4
+ include Alba::Resource
5
+
6
+ attributes :label
7
+
8
+ attribute :display_amount do |line|
9
+ line.display_amount.to_s
10
+ end
11
+
12
+ attribute :amount do |line|
13
+ line.amount.to_s
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ module Spree
2
+ module Emails
3
+ class AppliedPromotionSerializer < Spree::Api::V3::AppliedPromotionSerializer
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Spree
2
+ module Emails
3
+ class DeliveryMethodSerializer < Spree::Api::V3::DeliveryMethodSerializer
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,10 @@
1
+ module Spree
2
+ module Emails
3
+ class DeliveryRateSerializer < Spree::Api::V3::DeliveryRateSerializer
4
+ typelize freight_summary: [:EmailFreightSummary, nullable: true]
5
+
6
+ one :freight_summary, resource: Spree::Emails::FreightSummarySerializer
7
+ one :delivery_method, resource: Spree::Emails::DeliveryMethodSerializer
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,6 @@
1
+ module Spree
2
+ module Emails
3
+ class DeliverySerializer < Spree::Api::V3::DeliverySerializer
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Spree
2
+ module Emails
3
+ class FeeSerializer < Spree::Api::V3::FeeSerializer
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Spree
2
+ module Emails
3
+ class FreightSummarySerializer < Spree::Api::V3::FreightSummarySerializer
4
+ end
5
+ end
6
+ end