studio-engine 0.5.6 → 0.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -1
- data/README.md +8 -3
- data/app/helpers/studio_email_delivery_helper.rb +35 -0
- data/app/mailers/application_mailer.rb +1 -1
- data/lib/studio/version.rb +1 -1
- data/lib/studio.rb +31 -0
- data/lib/tasks/studio_ses.rake +30 -3
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 401a5db38e438ea4335d910bc67c88559739cb227a7377503c152cb2cea409ba
|
|
4
|
+
data.tar.gz: 12703eff81b8af83d9d73e26ebfbd7ec11b6a53444372c8330f32f7b09b89113
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8eff2ade70b7a335ac0f50a15e2085fa05a0ba4d5ae7ff36ff7a03c8ec811022fb4d6ece26fed7639c9faf3431d66a1548cc86ed0f261cf255d2dcd4c84ad51e
|
|
7
|
+
data.tar.gz: 72df2ccff7e151d391edd34c62d1a1e1ac1782317a14548f0fcfd541e58646dba89fc13ea254a1a141d1347d9016ee42e04b7c475a426eb9c8eb959725c6a588
|
data/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,32 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## v0.5.8 (2026-06-14)
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **`StudioEmailDeliveryHelper#email_delivery_banner_status`** — shared
|
|
11
|
+
non-production banner status text for whether the current process sends
|
|
12
|
+
external email and which transport is active (`resend`, `ses`, `capture`, or
|
|
13
|
+
the ActionMailer delivery method).
|
|
14
|
+
|
|
15
|
+
## v0.5.7 (2026-06-14)
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **`Studio.mailer_from_for_transport` / `Studio.marketing_from_for_transport`** —
|
|
19
|
+
provider-aware sender helpers. SES-ready apps use app/domain-specific
|
|
20
|
+
`MAILER_FROM` and `MARKETING_MAILER_FROM`; Resend fallback uses
|
|
21
|
+
`RESEND_MAILER_FROM`, defaulting to `McRitchie Studio <team@mcritchie.studio>`
|
|
22
|
+
so new apps can send during SES sandbox/presetup without verifying a second
|
|
23
|
+
Resend domain.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- **`ses:check` / `ses:verify_domain` credential selection** now prefers
|
|
27
|
+
`SES_AWS_ACCESS_KEY_ID` / `SES_AWS_SECRET_ACCESS_KEY` before falling back to
|
|
28
|
+
generic `AWS_*` credentials, keeping SES account checks separate from
|
|
29
|
+
consumer app S3/ImageCache IAM users.
|
|
30
|
+
- **`ses:verify_domain` existing identity handling** now accepts AWS SES's
|
|
31
|
+
`already exist` response wording and falls back to reading the existing
|
|
32
|
+
identity.
|
|
8
33
|
|
|
9
34
|
## v0.5.6 (2026-06-14)
|
|
10
35
|
|
data/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Shared Rails engine for McRitchie apps. Provides authentication, error handling,
|
|
|
11
11
|
gem "studio-engine", "~> 0.5"
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
Then `bundle install`. The current release is **v0.5.
|
|
14
|
+
Then `bundle install`. The current release is **v0.5.8**; see [`CHANGELOG.md`](./CHANGELOG.md) for the history.
|
|
15
15
|
|
|
16
16
|
> Published to RubyGems as of v0.4.0 (2026-05-17). New installs should use the RubyGems form, which the consumer Rails apps (`mcritchie-studio`, `turf-monster`) already use.
|
|
17
17
|
|
|
@@ -35,7 +35,9 @@ Studio.configure do |config|
|
|
|
35
35
|
config.auth_methods = %i[magic_link google]
|
|
36
36
|
config.registration_params = [:name, :email]
|
|
37
37
|
config.magic_link_token_name = "magic_link_my_app_v1"
|
|
38
|
-
config.mailer_from =
|
|
38
|
+
config.mailer_from = Studio.mailer_from_for_transport(
|
|
39
|
+
ses_from: "My App <team@example.com>"
|
|
40
|
+
)
|
|
39
41
|
config.theme_primary = "#4BAF50" # Override default violet
|
|
40
42
|
config.theme_logos = ["logo.svg"]
|
|
41
43
|
end
|
|
@@ -105,4 +107,7 @@ For short local experiments, temporarily point a consumer Gemfile at `path: "../
|
|
|
105
107
|
|
|
106
108
|
## Development Notes
|
|
107
109
|
|
|
108
|
-
|
|
110
|
+
Use the docs in [`docs/`](./docs) for engine setup, release, email transport,
|
|
111
|
+
and host-app contracts. Current cross-repo setup, ports, credentials, and
|
|
112
|
+
workflow guidance live in McRitchie Studio's
|
|
113
|
+
[`docs/agents/`](https://github.com/amcritchie/mcritchie-studio/tree/main/docs/agents).
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module StudioEmailDeliveryHelper
|
|
2
|
+
NON_DELIVERING_EMAIL_METHODS = %w[test file].freeze
|
|
3
|
+
|
|
4
|
+
def email_delivery_banner_status
|
|
5
|
+
delivery_method = studio_email_delivery_method
|
|
6
|
+
capture_enabled = Studio.local_email_capture?
|
|
7
|
+
sends_email = studio_email_perform_deliveries? && !capture_enabled &&
|
|
8
|
+
!NON_DELIVERING_EMAIL_METHODS.include?(delivery_method)
|
|
9
|
+
|
|
10
|
+
"EMAIL SEND #{sends_email} · #{email_delivery_transport_label(delivery_method, capture_enabled)}"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def email_delivery_transport_label(delivery_method = studio_email_delivery_method,
|
|
14
|
+
capture_enabled = Studio.local_email_capture?)
|
|
15
|
+
return "capture" if capture_enabled
|
|
16
|
+
return "ses" if Studio.ses_transport_ready?
|
|
17
|
+
return "resend" if delivery_method == "resend"
|
|
18
|
+
|
|
19
|
+
delivery_method.to_s.strip.empty? ? "unknown" : delivery_method
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def studio_email_delivery_method
|
|
25
|
+
return "unknown" unless defined?(ActionMailer)
|
|
26
|
+
|
|
27
|
+
ActionMailer::Base.delivery_method.to_s
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def studio_email_perform_deliveries?
|
|
31
|
+
return false unless defined?(ActionMailer)
|
|
32
|
+
|
|
33
|
+
ActionMailer::Base.perform_deliveries
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -3,5 +3,5 @@ class ApplicationMailer < ActionMailer::Base
|
|
|
3
3
|
# the host's config/initializers/studio.rb, with an ENV fallback. No layout is
|
|
4
4
|
# forced — ActionMailer renders bare if the host ships no mailer layout, and a
|
|
5
5
|
# host that defines its own ApplicationMailer (e.g. turf-monster) wins outright.
|
|
6
|
-
default from: -> { Studio.mailer_from || ENV["MAILER_FROM"] || "
|
|
6
|
+
default from: -> { Studio.mailer_from || ENV["MAILER_FROM"] || "McRitchie Studio <team@mcritchie.studio>" }
|
|
7
7
|
end
|
data/lib/studio/version.rb
CHANGED
data/lib/studio.rb
CHANGED
|
@@ -41,6 +41,7 @@ module Studio
|
|
|
41
41
|
# Default From: for engine-sent mail (magic links). Apps set this to their
|
|
42
42
|
# verified sending address in config/initializers/studio.rb.
|
|
43
43
|
mattr_accessor :mailer_from, default: nil
|
|
44
|
+
mattr_accessor :resend_mailer_from, default: "McRitchie Studio <team@mcritchie.studio>"
|
|
44
45
|
|
|
45
46
|
# Local/worktree email capture. nil means "auto": enabled when AGENT_WORKTREE
|
|
46
47
|
# is truthy, otherwise disabled. Production always disables capture.
|
|
@@ -88,6 +89,36 @@ module Studio
|
|
|
88
89
|
yield self
|
|
89
90
|
end
|
|
90
91
|
|
|
92
|
+
def self.mailer_from_for_transport(env: ENV, ses_from:, resend_from: nil)
|
|
93
|
+
if ses_transport_ready?(env)
|
|
94
|
+
env_value(env, "MAILER_FROM") || ses_from
|
|
95
|
+
else
|
|
96
|
+
env_value(env, "RESEND_MAILER_FROM") || resend_from || resend_mailer_from
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def self.marketing_from_for_transport(env: ENV, ses_from:, resend_from: nil)
|
|
101
|
+
if ses_transport_ready?(env)
|
|
102
|
+
env_value(env, "MARKETING_MAILER_FROM") || ses_from
|
|
103
|
+
else
|
|
104
|
+
env_value(env, "RESEND_MARKETING_FROM") ||
|
|
105
|
+
env_value(env, "RESEND_MAILER_FROM") ||
|
|
106
|
+
resend_from ||
|
|
107
|
+
resend_mailer_from
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def self.ses_transport_ready?(env = ENV)
|
|
112
|
+
env["MAIL_TRANSPORT"].to_s.downcase == "ses" &&
|
|
113
|
+
env_value(env, "SES_SMTP_USERNAME") &&
|
|
114
|
+
env_value(env, "SES_SMTP_PASSWORD")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def self.env_value(env, key)
|
|
118
|
+
value = env[key]
|
|
119
|
+
value if value && !value.to_s.strip.empty?
|
|
120
|
+
end
|
|
121
|
+
|
|
91
122
|
# True when the given sign-in method is enabled for this app.
|
|
92
123
|
def self.auth_method?(method)
|
|
93
124
|
auth_methods.include?(method.to_sym)
|
data/lib/tasks/studio_ses.rake
CHANGED
|
@@ -2,14 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
unless Rake::Task.task_defined?("ses:check")
|
|
4
4
|
namespace :ses do
|
|
5
|
+
def studio_ses_env_value(name)
|
|
6
|
+
value = ENV[name]
|
|
7
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
8
|
+
|
|
9
|
+
value
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def studio_ses_credential(name)
|
|
13
|
+
studio_ses_env_value("SES_#{name}") || studio_ses_env_value(name)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def studio_ses_credential_source(name)
|
|
17
|
+
return "SES_#{name}" if studio_ses_env_value("SES_#{name}")
|
|
18
|
+
return name if studio_ses_env_value(name)
|
|
19
|
+
|
|
20
|
+
"missing"
|
|
21
|
+
end
|
|
22
|
+
|
|
5
23
|
def studio_ses_signer(region)
|
|
6
24
|
require "aws-sigv4"
|
|
7
25
|
|
|
26
|
+
access_key_id = studio_ses_credential("AWS_ACCESS_KEY_ID")
|
|
27
|
+
secret_access_key = studio_ses_credential("AWS_SECRET_ACCESS_KEY")
|
|
28
|
+
missing = []
|
|
29
|
+
missing << "SES_AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY_ID" unless access_key_id
|
|
30
|
+
missing << "SES_AWS_SECRET_ACCESS_KEY or AWS_SECRET_ACCESS_KEY" unless secret_access_key
|
|
31
|
+
abort "ses:* needs #{missing.join(' and ')}." if missing.any?
|
|
32
|
+
|
|
8
33
|
Aws::Sigv4::Signer.new(
|
|
9
34
|
service: "ses",
|
|
10
35
|
region: region,
|
|
11
|
-
access_key_id:
|
|
12
|
-
secret_access_key:
|
|
36
|
+
access_key_id: access_key_id,
|
|
37
|
+
secret_access_key: secret_access_key
|
|
13
38
|
)
|
|
14
39
|
rescue LoadError
|
|
15
40
|
abort "ses:* needs aws-sigv4; it is usually available through aws-sdk-s3."
|
|
@@ -45,6 +70,7 @@ unless Rake::Task.task_defined?("ses:check")
|
|
|
45
70
|
|
|
46
71
|
code, account = get.call("/v2/email/account")
|
|
47
72
|
puts "GetAccount (region #{region}) -> HTTP #{code}"
|
|
73
|
+
puts " CredentialSource=#{studio_ses_credential_source('AWS_ACCESS_KEY_ID')}"
|
|
48
74
|
if code == 200
|
|
49
75
|
puts " SendingEnabled=#{account['SendingEnabled']} ProductionAccessEnabled=#{account['ProductionAccessEnabled']} Enforcement=#{account['EnforcementStatus']}"
|
|
50
76
|
else
|
|
@@ -74,8 +100,9 @@ unless Rake::Task.task_defined?("ses:check")
|
|
|
74
100
|
signer = studio_ses_signer(region)
|
|
75
101
|
body = { EmailIdentity: domain }.to_json
|
|
76
102
|
code, response = studio_ses_request(signer, region, "POST", "/v2/email/identities", body)
|
|
103
|
+
already_exists = "#{response['message']} #{response['Message']}".match?(/already exist/i)
|
|
77
104
|
|
|
78
|
-
if code == 409 ||
|
|
105
|
+
if code == 409 || ([400, 409].include?(code) && already_exists)
|
|
79
106
|
code, response = studio_ses_request(signer, region, "GET", "/v2/email/identities/#{domain}")
|
|
80
107
|
end
|
|
81
108
|
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: studio-engine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex McRitchie
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-06-14 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: rails
|
|
@@ -151,6 +152,7 @@ files:
|
|
|
151
152
|
- app/controllers/solana_sessions_controller.rb
|
|
152
153
|
- app/controllers/studio/local_emails_controller.rb
|
|
153
154
|
- app/controllers/theme_settings_controller.rb
|
|
155
|
+
- app/helpers/studio_email_delivery_helper.rb
|
|
154
156
|
- app/helpers/studio_theme_helper.rb
|
|
155
157
|
- app/jobs/error_log_cleanup_job.rb
|
|
156
158
|
- app/jobs/studio/email_delivery_job.rb
|
|
@@ -226,6 +228,7 @@ metadata:
|
|
|
226
228
|
source_code_uri: https://github.com/amcritchie/studio-engine/tree/main
|
|
227
229
|
bug_tracker_uri: https://github.com/amcritchie/studio-engine/issues
|
|
228
230
|
changelog_uri: https://github.com/amcritchie/studio-engine/blob/main/CHANGELOG.md
|
|
231
|
+
post_install_message:
|
|
229
232
|
rdoc_options: []
|
|
230
233
|
require_paths:
|
|
231
234
|
- lib
|
|
@@ -240,7 +243,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
240
243
|
- !ruby/object:Gem::Version
|
|
241
244
|
version: '0'
|
|
242
245
|
requirements: []
|
|
243
|
-
rubygems_version:
|
|
246
|
+
rubygems_version: 3.5.11
|
|
247
|
+
signing_key:
|
|
244
248
|
specification_version: 4
|
|
245
249
|
summary: Shared Rails engine providing auth, SSO, error logging, theming, and S3-backed
|
|
246
250
|
image caching
|