studio-engine 0.5.6 → 0.5.7
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 +18 -1
- data/README.md +8 -3
- 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 +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6f49e5815738a1a37139e2a5f0667daa9d2a3db14f301b7e627e441100258c3b
|
|
4
|
+
data.tar.gz: b5b94d83793c7b408332d99fc60c198dc07e7d6bb6cd342de060bb41dd5a34fd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3fe90400d7a5e3653fc6fe7db6de0ffd3b6e3d03bb84b56c4a1cf200313178725068c30e58fa36c243e8166c6ce32910a14f2ef319b1a126855beb09ea835527
|
|
7
|
+
data.tar.gz: e253f58c29679abc6d70bd66c73c6d83012f2e7dacc118a9312b13395317115bda7558e7f7be55fcaff05e64395b8f082f8284eae90fca90c4476a576bf83e9a
|
data/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,24 @@ 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.7 (2026-06-14)
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **`Studio.mailer_from_for_transport` / `Studio.marketing_from_for_transport`** —
|
|
11
|
+
provider-aware sender helpers. SES-ready apps use app/domain-specific
|
|
12
|
+
`MAILER_FROM` and `MARKETING_MAILER_FROM`; Resend fallback uses
|
|
13
|
+
`RESEND_MAILER_FROM`, defaulting to `McRitchie Studio <team@mcritchie.studio>`
|
|
14
|
+
so new apps can send during SES sandbox/presetup without verifying a second
|
|
15
|
+
Resend domain.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- **`ses:check` / `ses:verify_domain` credential selection** now prefers
|
|
19
|
+
`SES_AWS_ACCESS_KEY_ID` / `SES_AWS_SECRET_ACCESS_KEY` before falling back to
|
|
20
|
+
generic `AWS_*` credentials, keeping SES account checks separate from
|
|
21
|
+
consumer app S3/ImageCache IAM users.
|
|
22
|
+
- **`ses:verify_domain` existing identity handling** now accepts AWS SES's
|
|
23
|
+
`already exist` response wording and falls back to reading the existing
|
|
24
|
+
identity.
|
|
8
25
|
|
|
9
26
|
## v0.5.6 (2026-06-14)
|
|
10
27
|
|
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.7**; 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).
|
|
@@ -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
|
|