belt-messaging 0.1.1 → 0.2.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 +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +12 -0
- data/lib/belt/messaging/email.rb +22 -5
- data/lib/belt/messaging/version.rb +1 -1
- data/lib/belt/messaging.rb +5 -2
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e3f46b8a5c65296c353f37726532835ef2c4b65a988861f8d6c26efbf433a7d8
|
|
4
|
+
data.tar.gz: 64d9a505cfd10b21c606f78e7cea5c208147c6857bad19629e2a0b307e24a659
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 217d18276f86e6efd68ae4149a105a01e1a4d541afea332f22fd1e132dd1d39dbc0c21f01ecc16dc45637c30d5d6f6b7d4a809d74e6144adfd07eba465a7f289
|
|
7
|
+
data.tar.gz: 5b5161710702b061b8739c8145a2c92f2023958c73742a20a9bca90c51f1c5ac50582380e98d23da7f09a72fbcf4a9e74aabb0ca590b8f60f761a502eaba8eac
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
- `Belt::Messaging.send_email` accepts `headers:` (Hash of extra message headers) and `reply_to:` (String or Array). Headers go through SESv2 simple content, so you can set `List-Unsubscribe` + `List-Unsubscribe-Post: List-Unsubscribe=One-Click` (RFC 8058) and get the Gmail/Yahoo one-click unsubscribe button. Invalid reply-to addresses reject the send, same as invalid recipients.
|
|
6
|
+
- Require `aws-sdk-sesv2 ~> 1.45` (first release with SendEmail custom headers).
|
|
7
|
+
|
|
3
8
|
## 0.1.1
|
|
4
9
|
|
|
5
10
|
- Fix Terraform `email_send` IAM policy failing to apply on a fresh deploy: its `count` was gated on the SES identity ARN, a computed attribute unknown until apply, which raised `Invalid count argument`. Gate the policy document, policy resource, and `email_send_policy_arn` output on a plan-time-known local derived from input variables instead. Without this, the Lambda role never received `ses:SendEmail` and outbound transactional email silently never sent.
|
data/README.md
CHANGED
|
@@ -44,6 +44,18 @@ Belt::Messaging.send_email(
|
|
|
44
44
|
html: '<h1>Weekly Update</h1><p>HTML version...</p>'
|
|
45
45
|
)
|
|
46
46
|
|
|
47
|
+
# Reply-To and custom headers (e.g. RFC 8058 one-click unsubscribe)
|
|
48
|
+
Belt::Messaging.send_email(
|
|
49
|
+
to: 'user@example.com',
|
|
50
|
+
subject: 'You were invited',
|
|
51
|
+
body: 'Join us... Stop these emails: https://example.com/unsub?t=abc',
|
|
52
|
+
reply_to: 'support@example.com',
|
|
53
|
+
headers: {
|
|
54
|
+
'List-Unsubscribe' => '<https://example.com/unsub?t=abc>',
|
|
55
|
+
'List-Unsubscribe-Post' => 'List-Unsubscribe=One-Click'
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
|
|
47
59
|
# Phone number utilities
|
|
48
60
|
Belt::Messaging::PhoneFormatter.format_e164('(555) 123-4567') #=> '+15551234567'
|
|
49
61
|
Belt::Messaging::PhoneFormatter.valid?('555-123-4567') #=> true
|
data/lib/belt/messaging/email.rb
CHANGED
|
@@ -20,8 +20,12 @@ module Belt
|
|
|
20
20
|
# @param body [String] Email body (plain text)
|
|
21
21
|
# @param html [String, nil] Optional HTML body
|
|
22
22
|
# @param from [String, nil] Override the configured from address
|
|
23
|
+
# @param reply_to [String, Array<String>, nil] Reply-To address(es)
|
|
24
|
+
# @param headers [Hash{String => String}] Extra message headers, e.g.
|
|
25
|
+
# { 'List-Unsubscribe' => '<https://example.com/unsub?t=abc>',
|
|
26
|
+
# 'List-Unsubscribe-Post' => 'List-Unsubscribe=One-Click' }
|
|
23
27
|
# @return [Boolean] true if sent successfully, false otherwise
|
|
24
|
-
def self.send(to:, subject:, body:, html: nil, from: nil)
|
|
28
|
+
def self.send(to:, subject:, body:, html: nil, from: nil, reply_to: nil, headers: {})
|
|
25
29
|
config = Belt::Messaging.configuration
|
|
26
30
|
|
|
27
31
|
unless config.email_enabled?
|
|
@@ -48,6 +52,13 @@ module Belt
|
|
|
48
52
|
return false
|
|
49
53
|
end
|
|
50
54
|
|
|
55
|
+
reply_to_addresses = Array(reply_to)
|
|
56
|
+
invalid_reply_to = reply_to_addresses.reject { |email| valid_email?(email) }
|
|
57
|
+
if invalid_reply_to.any?
|
|
58
|
+
log(:error, 'Invalid reply-to address format', invalid: invalid_reply_to)
|
|
59
|
+
return false
|
|
60
|
+
end
|
|
61
|
+
|
|
51
62
|
from_address = from || config.email_from_address
|
|
52
63
|
if from_address.nil? || from_address.empty?
|
|
53
64
|
log(:error, 'EMAIL_FROM_ADDRESS not configured')
|
|
@@ -57,13 +68,16 @@ module Belt
|
|
|
57
68
|
begin
|
|
58
69
|
client = Aws::SESV2::Client.new(region: config.aws_region)
|
|
59
70
|
|
|
60
|
-
email_content = build_email_content(subject, body, html)
|
|
71
|
+
email_content = build_email_content(subject, body, html, headers)
|
|
61
72
|
|
|
62
|
-
|
|
73
|
+
params = {
|
|
63
74
|
from_email_address: from_address,
|
|
64
75
|
destination: { to_addresses: recipients },
|
|
65
76
|
content: { simple: email_content }
|
|
66
|
-
}
|
|
77
|
+
}
|
|
78
|
+
params[:reply_to_addresses] = reply_to_addresses if reply_to_addresses.any?
|
|
79
|
+
|
|
80
|
+
client.send_email(params)
|
|
67
81
|
|
|
68
82
|
log(:info, 'Email sent successfully', to: recipients, subject: subject)
|
|
69
83
|
true
|
|
@@ -91,7 +105,7 @@ module Belt
|
|
|
91
105
|
email.match?(/\A[^@\s]+@[^@\s]+\.[^@\s]+\z/)
|
|
92
106
|
end
|
|
93
107
|
|
|
94
|
-
def self.build_email_content(subject, body, html)
|
|
108
|
+
def self.build_email_content(subject, body, html, headers)
|
|
95
109
|
content = {
|
|
96
110
|
subject: { data: subject, charset: 'UTF-8' },
|
|
97
111
|
body: {
|
|
@@ -99,6 +113,9 @@ module Belt
|
|
|
99
113
|
}
|
|
100
114
|
}
|
|
101
115
|
content[:body][:html] = { data: html, charset: 'UTF-8' } if html
|
|
116
|
+
if headers && !headers.empty?
|
|
117
|
+
content[:headers] = headers.map { |name, value| { name: name.to_s, value: value.to_s } }
|
|
118
|
+
end
|
|
102
119
|
content
|
|
103
120
|
end
|
|
104
121
|
private_class_method :build_email_content
|
data/lib/belt/messaging.rb
CHANGED
|
@@ -49,9 +49,12 @@ module Belt
|
|
|
49
49
|
# @param body [String] Email body (plain text)
|
|
50
50
|
# @param html [String, nil] Optional HTML body
|
|
51
51
|
# @param from [String, nil] Override configured from address
|
|
52
|
+
# @param reply_to [String, Array<String>, nil] Reply-To address(es)
|
|
53
|
+
# @param headers [Hash{String => String}] Extra message headers (e.g. List-Unsubscribe)
|
|
52
54
|
# @return [Boolean] true if sent successfully
|
|
53
|
-
def send_email(to:, subject:, body:, html: nil, from: nil)
|
|
54
|
-
Email.send(to: to, subject: subject, body: body, html: html, from: from
|
|
55
|
+
def send_email(to:, subject:, body:, html: nil, from: nil, reply_to: nil, headers: {})
|
|
56
|
+
Email.send(to: to, subject: subject, body: body, html: html, from: from,
|
|
57
|
+
reply_to: reply_to, headers: headers)
|
|
55
58
|
end
|
|
56
59
|
end
|
|
57
60
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: belt-messaging
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stowzilla
|
|
@@ -43,14 +43,14 @@ dependencies:
|
|
|
43
43
|
requirements:
|
|
44
44
|
- - "~>"
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '1.
|
|
46
|
+
version: '1.45'
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
51
|
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '1.
|
|
53
|
+
version: '1.45'
|
|
54
54
|
description: Belt plugin providing SMS and email messaging. SMS via AWS PinpointSMSVoiceV2,
|
|
55
55
|
email via AWS SESv2. Includes phone verification, opt-in/opt-out handling, and Terraform
|
|
56
56
|
infrastructure.
|