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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7dde49831782823962437f0efab2c8334fe0efc4376873ee9b9c4c2486aa4d1f
4
- data.tar.gz: c9cb4750ad4298869623db8a98e93662b101a1b2ce055fb779d15a3095ba1529
3
+ metadata.gz: 6f49e5815738a1a37139e2a5f0667daa9d2a3db14f301b7e627e441100258c3b
4
+ data.tar.gz: b5b94d83793c7b408332d99fc60c198dc07e7d6bb6cd342de060bb41dd5a34fd
5
5
  SHA512:
6
- metadata.gz: bb82ea299130783431b2dfea4fac8ddece0728b7f49d6ea15c1cff49d819fb6687a5a03f4eb80d4a0f8cb590cf78e45ccb1cdec410bc042ba6caed95bdde24b0
7
- data.tar.gz: e93039393ac8e344f7eef097ce55629f41cdefdb8f48be418ad3f8a7228e1c01363a3646ecd23708cffa182d3e905e17a8c1b2f8eccbfe6b97a3c9ef203a855b
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
- No entries yet.
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.5**; see [`CHANGELOG.md`](./CHANGELOG.md) for the history.
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 = ENV.fetch("MAILER_FROM", "noreply@example.com")
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
- See [CLAUDE.md](./CLAUDE.md) only as legacy migration context while neutral agent docs are being extracted. Current cross-repo setup, ports, credentials, and workflow guidance live in McRitchie Studio's [`docs/agents/`](https://github.com/amcritchie/mcritchie-studio/tree/main/docs/agents).
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"] || "no-reply@mcritchie.studio" }
6
+ default from: -> { Studio.mailer_from || ENV["MAILER_FROM"] || "McRitchie Studio <team@mcritchie.studio>" }
7
7
  end
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.5.6"
2
+ VERSION = "0.5.7"
3
3
  end
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)
@@ -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: ENV.fetch("AWS_ACCESS_KEY_ID"),
12
- secret_access_key: ENV.fetch("AWS_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 || "#{response['message']}#{response['Message']}".match?(/already exists/i)
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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: studio-engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.6
4
+ version: 0.5.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie