belt-messaging 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 57f12b22980eee716c98dcd00612c63b7b72a964cb3fef007fc0c1e594893cef
4
+ data.tar.gz: 81c05386abb62cfd11aceb9c68fb089dc81e5aa870904cfad5f27920d7bf6590
5
+ SHA512:
6
+ metadata.gz: add692f4eed8cf815613228ed61aa4e9905c9be7bf07289e7f31e736ae943fae216749938282f766bb39a848f4ea7aab5f6e0bfa8e7e2bf85a02c600b68f8910
7
+ data.tar.gz: b016a16412ad2bfad897cddd6dd3a765e1a2a0a4760bd1229d960843021331e6964d5512236c09679a0a2ed396a3e6ab8f29e07bc035793b380ba6d1abd9ba96
data/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Add `Belt::Messaging.send_email(to:, subject:, body:, html:)` — send email via AWS SESv2
6
+ - Add `Belt::Messaging::Email` — core email sending module
7
+ - Add `Belt::Messaging::EmailGate` — mode-based send gating (none/whitelist/all)
8
+ - Add `Belt::Messaging::Logging` — shared logging concern extracted from all modules
9
+ - Add email configuration: `ENABLE_EMAIL`, `EMAIL_MODE`, `EMAIL_WHITELIST`, `EMAIL_FROM_ADDRESS`
10
+ - Add `aws-sdk-sesv2` dependency
11
+ - Terraform module: SES email identity verification (domain or address), email send IAM policy
12
+ - Terraform outputs: `email_identity`, `email_send_policy_arn`, `email_domain_dkim_tokens`
13
+
14
+ ## 0.0.1
15
+
16
+ - Initial release
17
+ - `Belt::Messaging.send_sms(to:, message:)` — send SMS via AWS PinpointSMSVoiceV2
18
+ - `Belt::Messaging::PhoneFormatter` — E.164 phone number formatting
19
+ - `Belt::Messaging::SmsGate` — mode-based send gating (none/whitelist/all)
20
+ - `Belt::Messaging::Configuration` — environment-based configuration
21
+ - Generator: `belt g messaging` — installs Terraform module, Lambda entry point, controllers
22
+ - Destroy: `belt d messaging` — cleanly removes all generated files
23
+ - Default controllers (Devise-style — work from gem, override by generating)
24
+ - Terraform module: Pinpoint toll-free number, two-way messaging, SNS, IAM
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 stowzilla
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,224 @@
1
+ # belt-messaging
2
+
3
+ SMS and email messaging for [Belt](https://github.com/stowzilla/belt) applications via AWS.
4
+
5
+ - **SMS** via AWS End User Messaging (PinpointSMSVoiceV2)
6
+ - **Email** via AWS Simple Email Service (SESv2)
7
+
8
+ ## Installation
9
+
10
+ Add to your Gemfile:
11
+
12
+ ```ruby
13
+ gem 'belt-messaging'
14
+ ```
15
+
16
+ Then:
17
+
18
+ ```bash
19
+ bundle install
20
+ belt generate messaging
21
+ ```
22
+
23
+ ## What You Get
24
+
25
+ ### From the gem (no generation needed)
26
+
27
+ ```ruby
28
+ # Send an SMS from anywhere in your app
29
+ Belt::Messaging.send_sms(to: '+15551234567', message: 'Your order is ready!')
30
+ Belt::Messaging.send_sms(to: '555-123-4567', message: 'Hello!') # auto-formats to E.164
31
+
32
+ # Send an email from anywhere in your app
33
+ Belt::Messaging.send_email(
34
+ to: 'user@example.com',
35
+ subject: 'Welcome!',
36
+ body: 'Thanks for signing up.'
37
+ )
38
+
39
+ # With HTML body
40
+ Belt::Messaging.send_email(
41
+ to: ['user1@example.com', 'user2@example.com'],
42
+ subject: 'Weekly Update',
43
+ body: 'Plain text version...',
44
+ html: '<h1>Weekly Update</h1><p>HTML version...</p>'
45
+ )
46
+
47
+ # Phone number utilities
48
+ Belt::Messaging::PhoneFormatter.format_e164('(555) 123-4567') #=> '+15551234567'
49
+ Belt::Messaging::PhoneFormatter.valid?('555-123-4567') #=> true
50
+
51
+ # Email validation
52
+ Belt::Messaging::Email.valid_email?('user@example.com') #=> true
53
+ ```
54
+
55
+ ### From the generator (`belt g messaging`)
56
+
57
+ - **Terraform module** — Pinpoint phone number, SES identity, SNS topics, IAM policies
58
+ - **Lambda entry point** — Dedicated Lambda for inbound SMS (STOP/START handling)
59
+ - **Controllers** — SMS verification + messaging events (override to customize)
60
+ - **Route injection** — Adds `sms-verification` to your routes
61
+
62
+ ## Configuration
63
+
64
+ Environment variables:
65
+
66
+ ### SMS Configuration
67
+
68
+ | Variable | Purpose | Default |
69
+ |----------|---------|---------|
70
+ | `ENABLE_SMS` | Master switch for sending SMS | `false` |
71
+ | `SMS_MODE` | Gating mode: `none`, `whitelist`, `all` | `none` |
72
+ | `SMS_WHITELIST` | Comma-separated E.164 numbers (for whitelist mode) | — |
73
+ | `SMS_ORIGINATION_NUMBER` | Your toll-free number (E.164) | — |
74
+
75
+ ### Email Configuration
76
+
77
+ | Variable | Purpose | Default |
78
+ |----------|---------|---------|
79
+ | `ENABLE_EMAIL` | Master switch for sending email | `false` |
80
+ | `EMAIL_MODE` | Gating mode: `none`, `whitelist`, `all` | `none` |
81
+ | `EMAIL_WHITELIST` | Comma-separated addresses (for whitelist mode) | — |
82
+ | `EMAIL_FROM_ADDRESS` | Your verified sender address | — |
83
+ | `AWS_REGION` | AWS region for SES/Pinpoint | `us-east-1` |
84
+
85
+ ### Programmatic configuration
86
+
87
+ ```ruby
88
+ Belt::Messaging.configure do |config|
89
+ # SMS
90
+ config.sms_mode = 'whitelist'
91
+ config.sms_whitelist = ['+15551234567']
92
+ config.origination_number = '+18005550000'
93
+
94
+ # Email
95
+ config.email_mode = 'whitelist'
96
+ config.email_whitelist = ['dev@example.com']
97
+ config.email_from_address = 'noreply@example.com'
98
+ end
99
+ ```
100
+
101
+ ## Messaging Modes
102
+
103
+ Both SMS and email use the same gating pattern:
104
+
105
+ | Mode | Behavior |
106
+ |------|----------|
107
+ | `none` | Never send (safe default for development) |
108
+ | `whitelist` | Only send to addresses in the whitelist |
109
+ | `all` | Send to everyone (production) |
110
+
111
+ ## Customizing Controllers
112
+
113
+ By default, the gem provides controllers that handle SMS verification and opt-in/opt-out.
114
+ To customize behavior, generate controller overrides into your app:
115
+
116
+ ```bash
117
+ belt g messaging --controllers # Generate overrides you can edit
118
+ ```
119
+
120
+ The generated controllers are scaffolds with TODO comments showing where to add your persistence logic.
121
+ If you don't generate them, the gem's built-in controllers handle everything with sensible defaults.
122
+
123
+ ## Terraform Module
124
+
125
+ After running `belt g messaging`, add the module to your environment's `main.tf`:
126
+
127
+ ```hcl
128
+ module "messaging" {
129
+ source = "../modules/messaging"
130
+ app_name = var.app_name
131
+ environment = var.environment
132
+ aws_region = var.aws_region
133
+
134
+ # SMS Configuration
135
+ enable_sms = true
136
+
137
+ # Optional: request a new toll-free number
138
+ # request_toll_free_number = true
139
+
140
+ # Optional: use a shared number for dev
141
+ # sms_origination_number = "+18005550000"
142
+
143
+ # Email Configuration
144
+ enable_email = true
145
+
146
+ # Option 1: Verify entire domain (recommended for production)
147
+ # email_domain = "example.com"
148
+
149
+ # Option 2: Verify single address (quick setup)
150
+ email_from_address = "noreply@example.com"
151
+ }
152
+ ```
153
+
154
+ ### Email Identity Verification
155
+
156
+ For **domain verification** (production):
157
+ 1. Set `email_domain = "your-domain.com"` and deploy
158
+ 2. Add the DKIM DNS records output by Terraform
159
+ 3. Wait for verification (usually minutes)
160
+ 4. Set `EMAIL_FROM_ADDRESS` to any address on that domain
161
+
162
+ For **address verification** (quick dev setup):
163
+ 1. Set `email_from_address = "noreply@example.com"` and deploy
164
+ 2. Check the inbox and click the verification link
165
+ 3. Set `EMAIL_FROM_ADDRESS` to the verified address
166
+
167
+ ### SES Sandbox
168
+
169
+ New AWS accounts start in the SES sandbox, which limits sending to verified addresses only.
170
+ For production, request production access in the AWS Console → SES → Account Dashboard.
171
+
172
+ ## AWS Approval Process
173
+
174
+ ### SMS: Toll-Free Verification (Recommended)
175
+
176
+ 1. Set `request_toll_free_number = true` and deploy
177
+ 2. Go to AWS Console → End User Messaging → Registrations
178
+ 3. Submit toll-free verification:
179
+ - Company name and address
180
+ - Use case description
181
+ - Sample messages
182
+ 4. Wait 2-15 business days for approval
183
+ 5. Once ACTIVE, update `SMS_ORIGINATION_NUMBER` with the new number
184
+
185
+ **Cost:** ~$2/month + $0.0075/message segment
186
+
187
+ ### SMS: 10DLC (Higher Volume)
188
+
189
+ For higher throughput at lower per-message cost:
190
+
191
+ 1. Register your brand in the 10DLC registry
192
+ 2. Create a campaign describing your use case
193
+ 3. Provision a 10DLC number
194
+ 4. Update configuration
195
+
196
+ **Cost:** $0.0025-$0.005/segment, requires brand ($4/month) + campaign registration
197
+
198
+ ### SMS: Short Code (Highest Throughput)
199
+
200
+ For very high volume (100+ messages/second):
201
+
202
+ 1. Apply for a short code through AWS
203
+ 2. Complete carrier vetting process
204
+ 3. Wait 8-12 weeks for approval
205
+
206
+ **Cost:** $1000+/month
207
+
208
+ ### Email: SES Pricing
209
+
210
+ - **First 62,000 emails/month:** Free (from EC2/Lambda)
211
+ - **After that:** $0.10 per 1,000 emails
212
+ - Attachments: $0.12 per GB
213
+
214
+ ## Removing
215
+
216
+ ```bash
217
+ belt destroy messaging
218
+ ```
219
+
220
+ Then remove the module reference from your environment `main.tf` files.
221
+
222
+ ## License
223
+
224
+ MIT
@@ -0,0 +1,316 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'erb'
5
+
6
+ module Belt
7
+ module Generators
8
+ class MessagingGenerator
9
+ TEMPLATE_DIR = File.expand_path('../messaging/templates', __dir__)
10
+
11
+ def self.description
12
+ 'Install two-way SMS messaging (AWS PinpointSMSVoiceV2)'
13
+ end
14
+
15
+ def self.run(args)
16
+ if args.include?('--help') || args.include?('-h')
17
+ print_help
18
+ return
19
+ end
20
+
21
+ new(args).generate
22
+ end
23
+
24
+ def self.destroy(args)
25
+ new(args).destroy
26
+ end
27
+
28
+ def self.print_help
29
+ puts <<~HELP
30
+ Install two-way SMS messaging infrastructure for your Belt app.
31
+
32
+ Usage: belt generate messaging [options]
33
+
34
+ Options:
35
+ --controllers Generate controller overrides (to customize behavior)
36
+ --force Overwrite existing files
37
+
38
+ What gets created:
39
+ infrastructure/modules/messaging/ Terraform module (Pinpoint, SNS, IAM)
40
+ config/lambda/messaging_events.yml Lambda configuration (timeout, memory, env, triggers)
41
+ config/routes.tf.rb Routes for sms-verification (updated)
42
+ lambda/messaging_events.rb Lambda entry point for inbound SMS
43
+
44
+ What stays in the gem (no generation needed):
45
+ Belt::Messaging.send_sms(to:, message:) Send SMS from anywhere
46
+ Belt::Messaging::PhoneFormatter E.164 formatting
47
+ Belt::Messaging::SmsGate Whitelist/mode gating
48
+ SmsVerificationController Phone verification (gem default)
49
+ SmsEventsController Opt-in/opt-out handling (gem default)
50
+
51
+ To override controllers (like `rails g devise:views`):
52
+ belt g messaging --controllers
53
+
54
+ After generation:
55
+ 1. Add module reference to your environment's main.tf
56
+ 2. Configure environment variables (ENABLE_SMS, SMS_MODE, etc.)
57
+ 3. Deploy: belt apply <env>
58
+ 4. Request toll-free number via AWS console (if not already acquired)
59
+
60
+ Examples:
61
+ belt g messaging # Infrastructure only (use gem defaults)
62
+ belt g messaging --controllers # Also generate controller overrides
63
+ belt d messaging
64
+ HELP
65
+ end
66
+
67
+ def initialize(args)
68
+ @force = args.include?('--force')
69
+ @with_controllers = args.include?('--controllers')
70
+ @app_name = detect_namespace
71
+ end
72
+
73
+ def generate
74
+ generate_terraform_module
75
+ generate_lambda_config
76
+ generate_lambda_entry_point
77
+ generate_controllers if @with_controllers
78
+ inject_routes
79
+ print_success
80
+ end
81
+
82
+ def destroy
83
+ remove_terraform_module
84
+ remove_lambda_config
85
+ remove_lambda_entry_point
86
+ remove_controllers
87
+ remove_routes
88
+ puts "\n✓ Messaging removed!"
89
+ puts " Don't forget to remove the module reference from your environment main.tf files."
90
+ end
91
+
92
+ private
93
+
94
+ def detect_namespace
95
+ routes_file = find_routes_file_path
96
+ if routes_file && File.exist?(routes_file)
97
+ match = File.read(routes_file).match(/namespace :(\w+)/)
98
+ return match[1] if match
99
+ end
100
+ File.basename(Dir.pwd)
101
+ end
102
+
103
+ # Finds routes.tf.rb checking config/ first, then infrastructure/ (legacy).
104
+ def find_routes_file_path
105
+ candidates = ['config/routes.tf.rb', 'infrastructure/routes.tf.rb']
106
+ candidates.find { |f| File.exist?(f) }
107
+ end
108
+
109
+ # ---- Generate ----
110
+
111
+ def generate_terraform_module
112
+ module_dir = 'infrastructure/modules/messaging'
113
+
114
+ if Dir.exist?(module_dir) && !@force
115
+ puts " skip #{module_dir}/ (already exists, use --force to overwrite)"
116
+ return
117
+ end
118
+
119
+ FileUtils.mkdir_p(module_dir)
120
+
121
+ write_template('terraform/main.tf.erb', "#{module_dir}/main.tf")
122
+ write_template('terraform/variables.tf.erb', "#{module_dir}/variables.tf")
123
+ write_template('terraform/outputs.tf.erb', "#{module_dir}/outputs.tf")
124
+
125
+ puts " create #{module_dir}/main.tf"
126
+ puts " create #{module_dir}/variables.tf"
127
+ puts " create #{module_dir}/outputs.tf"
128
+ end
129
+
130
+ def generate_lambda_config
131
+ config_dir = 'config/lambda'
132
+ dest = "#{config_dir}/messaging_events.yml"
133
+
134
+ if File.exist?(dest) && !@force
135
+ puts " skip #{dest} (already exists)"
136
+ return
137
+ end
138
+
139
+ FileUtils.mkdir_p(config_dir)
140
+ write_template('config/messaging_events.yml.erb', dest)
141
+ puts " create #{dest}"
142
+ end
143
+
144
+ def generate_lambda_entry_point
145
+ dest = 'lambda/messaging_events.rb'
146
+
147
+ if File.exist?(dest) && !@force
148
+ puts " skip #{dest} (already exists)"
149
+ return
150
+ end
151
+
152
+ write_template('lambda/messaging_events.rb.erb', dest)
153
+ puts " create #{dest}"
154
+ end
155
+
156
+ def generate_controllers
157
+ controller_dir = "lambda/controllers/#{@app_name}"
158
+ FileUtils.mkdir_p(controller_dir)
159
+
160
+ # SMS Verification controller
161
+ verification_dest = "#{controller_dir}/sms_verification_controller.rb"
162
+ if File.exist?(verification_dest) && !@force
163
+ puts " skip #{verification_dest} (already exists)"
164
+ else
165
+ write_template('controllers/sms_verification_controller.rb.erb', verification_dest)
166
+ puts " create #{verification_dest}"
167
+ end
168
+
169
+ # SMS Events controller (for override)
170
+ events_dir = 'lambda/controllers/messaging_events'
171
+ FileUtils.mkdir_p(events_dir)
172
+
173
+ events_dest = "#{events_dir}/messaging_events_controller.rb"
174
+ if File.exist?(events_dest) && !@force
175
+ puts " skip #{events_dest} (already exists)"
176
+ else
177
+ write_template('controllers/messaging_events_controller.rb.erb', events_dest)
178
+ puts " create #{events_dest}"
179
+ end
180
+ end
181
+
182
+ def inject_routes
183
+ routes_file = find_routes_file_path
184
+ return unless routes_file && File.exist?(routes_file)
185
+
186
+ content = File.read(routes_file)
187
+
188
+ # Add sms-verification route to app namespace
189
+ unless content.include?('sms_verification') || content.include?('sms-verification')
190
+ namespace_pattern = /^(\s*)namespace :#{Regexp.escape(@app_name)}\b[^\n]*do\s*\n(.*?)^\1end/m
191
+ if content.match?(namespace_pattern)
192
+ sms_route = " resource :sms_verification, only: [:create, :update]"
193
+ content.sub!(namespace_pattern) do |match|
194
+ indent = ::Regexp.last_match(1)
195
+ match.sub(/^(#{indent})end\z/m, "#{sms_route}\n#{indent}end")
196
+ end
197
+ end
198
+ end
199
+
200
+ File.write(routes_file, content)
201
+ puts " update #{routes_file}"
202
+ end
203
+
204
+ # ---- Destroy ----
205
+
206
+ def remove_terraform_module
207
+ module_dir = 'infrastructure/modules/messaging'
208
+ if Dir.exist?(module_dir)
209
+ FileUtils.rm_rf(module_dir)
210
+ puts " remove #{module_dir}/"
211
+ end
212
+ end
213
+
214
+ def remove_lambda_config
215
+ path = 'config/lambda/messaging_events.yml'
216
+ if File.exist?(path)
217
+ File.delete(path)
218
+ puts " remove #{path}"
219
+ end
220
+ end
221
+
222
+ def remove_lambda_entry_point
223
+ path = 'lambda/messaging_events.rb'
224
+ if File.exist?(path)
225
+ File.delete(path)
226
+ puts " remove #{path}"
227
+ end
228
+ end
229
+
230
+ def remove_controllers
231
+ events_dir = 'lambda/controllers/messaging_events'
232
+ if Dir.exist?(events_dir)
233
+ FileUtils.rm_rf(events_dir)
234
+ puts " remove #{events_dir}/"
235
+ end
236
+
237
+ verification = "lambda/controllers/#{@app_name}/sms_verification_controller.rb"
238
+ if File.exist?(verification)
239
+ File.delete(verification)
240
+ puts " remove #{verification}"
241
+ end
242
+ end
243
+
244
+ def remove_routes
245
+ routes_file = find_routes_file_path
246
+ return unless routes_file && File.exist?(routes_file)
247
+
248
+ content = File.read(routes_file)
249
+ original = content.dup
250
+
251
+ content.gsub!(/^\s*resource :sms_verification.*\n/, '')
252
+
253
+ if content != original
254
+ File.write(routes_file, content)
255
+ puts " update #{routes_file}"
256
+ end
257
+ end
258
+
259
+ def write_template(template_name, dest_path)
260
+ template_path = File.join(TEMPLATE_DIR, template_name)
261
+ FileUtils.mkdir_p(File.dirname(dest_path))
262
+ content = ERB.new(File.read(template_path), trim_mode: '-').result(binding)
263
+ File.write(dest_path, content)
264
+ end
265
+
266
+ def print_success
267
+ puts <<~SUCCESS
268
+
269
+ ✓ Messaging installed!
270
+
271
+ Next steps:
272
+ 1. Add the module to your environment's main.tf:
273
+
274
+ module "messaging" {
275
+ source = "../modules/messaging"
276
+ app_name = var.app_name
277
+ environment = var.environment
278
+ aws_region = var.aws_region
279
+ }
280
+
281
+ 2. Configure environment variables:
282
+ ENABLE_SMS=true
283
+ SMS_MODE=whitelist
284
+ SMS_WHITELIST=+15551234567
285
+ SMS_ORIGINATION_NUMBER=+1XXXXXXXXXX
286
+
287
+ 3. Deploy:
288
+ belt apply <env>
289
+
290
+ 4. Request a toll-free number (if needed):
291
+ Set request_toll_free_number = true in your module call,
292
+ or provide sms_origination_number for a shared number.
293
+
294
+ 5. Complete AWS Toll-Free Verification:
295
+ - Go to AWS Console → End User Messaging → Registrations
296
+ - Submit toll-free verification (company info, use case, sample messages)
297
+ - Typical approval: 2-15 business days
298
+
299
+ 6. Test:
300
+ belt c <env> --run "Belt::Messaging.send_sms(to: '+15551234567', message: 'Hello!')"
301
+
302
+ SMS Modes:
303
+ none — Never send (default, safe for dev)
304
+ whitelist — Only send to numbers in SMS_WHITELIST
305
+ all — Send to everyone (production)
306
+
307
+ Documentation:
308
+ Toll-Free: Simplest approval, ~$2/mo + $0.0075/segment
309
+ 10DLC: Cheaper at volume, requires brand + campaign registration
310
+ Short Code: Highest throughput, $1000+/mo, lengthy approval
311
+
312
+ SUCCESS
313
+ end
314
+ end
315
+ end
316
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Belt
4
+ module Messaging
5
+ class Configuration
6
+ # SMS configuration
7
+ attr_accessor :aws_region, :origination_number, :sms_mode, :sms_whitelist,
8
+ :opt_in_message, :logger
9
+
10
+ # Email configuration
11
+ attr_accessor :email_from_address, :email_mode, :email_whitelist
12
+
13
+ def initialize
14
+ @aws_region = ENV['AWS_REGION'] || 'us-east-1'
15
+
16
+ # SMS
17
+ @origination_number = ENV['SMS_ORIGINATION_NUMBER']
18
+ @sms_mode = ENV['SMS_MODE'] || 'none'
19
+ @sms_whitelist = (ENV['SMS_WHITELIST'] || '').split(',').map(&:strip)
20
+ @opt_in_message = 'You have been re-subscribed to SMS notifications.'
21
+
22
+ # Email
23
+ @email_from_address = ENV['EMAIL_FROM_ADDRESS']
24
+ @email_mode = ENV['EMAIL_MODE'] || 'none'
25
+ @email_whitelist = (ENV['EMAIL_WHITELIST'] || '').split(',').map(&:strip)
26
+
27
+ @logger = nil # Falls back to Belt::Observability::Logger if available
28
+ end
29
+
30
+ # SMS enabled check
31
+ def enabled?
32
+ ENV['ENABLE_SMS'] == 'true'
33
+ end
34
+
35
+ # Email enabled check
36
+ def email_enabled?
37
+ ENV['ENABLE_EMAIL'] == 'true'
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Belt
4
+ module Messaging
5
+ module Controllers
6
+ # Default SMS events controller for handling inbound opt-out/opt-in messages.
7
+ # This controller processes STOP/START keywords from two-way messaging.
8
+ #
9
+ # To customize behavior, generate the controller override:
10
+ # belt g messaging
11
+ #
12
+ # Then modify lambda/controllers/messaging_events/messaging_events_controller.rb
13
+ class MessagingEventsController < BeltController::Base
14
+ OPT_OUT_KEYWORDS = %w[ARRET CANCEL END OPT-OUT OPTOUT QUIT REMOVE STOP TD UNSUBSCRIBE].freeze
15
+ OPT_IN_KEYWORDS = %w[START UNSTOP].freeze
16
+
17
+ def opt_out
18
+ phone = sms_origin_phone
19
+
20
+ Belt::Messaging.log(:info, 'Processing SMS opt-out', phone: phone)
21
+
22
+ # Default: log the opt-out. Users should override this controller
23
+ # to update their own user/customer model.
24
+ Belt::Messaging.log(:warn,
25
+ 'SMS opt-out received but no handler configured. Override MessagingEventsController to handle.',
26
+ phone: phone
27
+ )
28
+
29
+ success_response(success: true, processed: false, reason: 'no_handler_configured')
30
+ end
31
+
32
+ def opt_in
33
+ phone = sms_origin_phone
34
+
35
+ Belt::Messaging.log(:info, 'Processing SMS opt-in', phone: phone)
36
+
37
+ Belt::Messaging.log(:warn,
38
+ 'SMS opt-in received but no handler configured. Override MessagingEventsController to handle.',
39
+ phone: phone
40
+ )
41
+
42
+ success_response(success: true, processed: false, reason: 'no_handler_configured')
43
+ end
44
+
45
+ private
46
+
47
+ def sms_origin_phone
48
+ records = @event['Records']
49
+ return nil unless records&.first
50
+
51
+ message = JSON.parse(records.first.dig('Sns', 'Message') || '{}')
52
+ message['originationNumber']
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end