mailchannels-email-api 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 +7 -0
- data/LICENSE +21 -0
- data/README.md +83 -0
- data/lib/mailchannels/email_api.rb +104 -0
- metadata +89 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2f503dd4bf6d415eebb3c7f6ebc5f4acc7caee1d81ba34b5239bc1e4624c7d6c
|
|
4
|
+
data.tar.gz: 1626a60d75bed6e350f40f074c97aba5cd50e04056f6d3272e3e8a6d63cad522
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: f464babb4c98ec3100a60b447e2374fff0065c9d7599aeb7ce86a6a5ecc2d09989e505f8f26e9c47b52da9450681a9dd4c1454d405efd21f41922b18d772beb8
|
|
7
|
+
data.tar.gz: 50cda827d1c96c571be8cff22cc79bdc21c0d7f6977aa1963c17ca3d5c0179e34f62d947f2b753cf4710c5c4f8dafd98c0f4bf350219a770f164f932e8265bcf
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MailChannels Corporation
|
|
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,83 @@
|
|
|
1
|
+
# MailChannels Email API Ruby client
|
|
2
|
+
|
|
3
|
+
Published on RubyGems as [`mailchannels-email-api`](https://rubygems.org/gems/mailchannels-email-api).
|
|
4
|
+
This repository is the public development source.
|
|
5
|
+
Support: dev@mailchannels.com. Report reproducible bugs through this repository’s issues.
|
|
6
|
+
|
|
7
|
+
This Ruby 3.2+ client calls the current MailChannels Email API directly with an
|
|
8
|
+
API key. It accepts the API's full send payload, including personalizations,
|
|
9
|
+
plain text or HTML content, attachments, and custom headers. It does not need a
|
|
10
|
+
Cloudflare Worker. It is a sending client, not an Action Mailer delivery backend.
|
|
11
|
+
|
|
12
|
+
## Try a validation request
|
|
13
|
+
|
|
14
|
+
Install the gem:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
gem install mailchannels-email-api
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Provide `MAILCHANNELS_API_KEY` through your environment or secret store. Configure
|
|
21
|
+
your sender domain and use an allowed recipient. The free plan permits 100
|
|
22
|
+
emails/day and restricts recipients to verified account users until a payment
|
|
23
|
+
method is added.
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
require 'mailchannels/email_api'
|
|
27
|
+
|
|
28
|
+
client = MailChannels::EmailAPI::Client.new
|
|
29
|
+
message = {
|
|
30
|
+
from: { email: ENV.fetch('MAILCHANNELS_SENDER') },
|
|
31
|
+
personalizations: [
|
|
32
|
+
{ to: [{ email: ENV.fetch('MAILCHANNELS_RECIPIENT') }] }
|
|
33
|
+
],
|
|
34
|
+
subject: 'MailChannels Ruby validation',
|
|
35
|
+
content: [{ type: 'text/plain', value: 'Validate this message without sending.' }]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
result = client.validate_email(message)
|
|
39
|
+
puts result.validated? # HTTP 200, no email sent
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
To send deliberately, call `client.send_email(message)`. An HTTP 202 result has
|
|
43
|
+
`accepted? == true`; it means accepted for processing, not delivered. A dry run
|
|
44
|
+
is HTTP 200 and never reports acceptance. Unexpected statuses, including
|
|
45
|
+
redirects, raise `MailChannels::EmailAPI::APIError` with a `status` attribute.
|
|
46
|
+
|
|
47
|
+
Payloads use the [API's JSON schema](https://docs.mailchannels.com/email-api.yaml)
|
|
48
|
+
without shorthand conversion. Both string and symbol Hash keys serialize to
|
|
49
|
+
JSON keys. See the [API documentation](https://docs.mailchannels.com/email-api/overview)
|
|
50
|
+
for attachment encoding and other supported fields.
|
|
51
|
+
|
|
52
|
+
## Failure handling and lifecycle
|
|
53
|
+
|
|
54
|
+
The client opens and closes an HTTPS session per call. TLS peer verification is
|
|
55
|
+
enabled. Connect timeout is 10 seconds; read and write timeouts are 30 seconds
|
|
56
|
+
each. These are I/O timeouts, not a total request deadline. It does not follow
|
|
57
|
+
redirects, use environment proxies, or automatically retry requests.
|
|
58
|
+
|
|
59
|
+
An ambiguous transport failure can occur after the server accepts a message.
|
|
60
|
+
Check delivery records before retrying. `TransportError` has a sanitized message
|
|
61
|
+
and does not retain the underlying exception as its cause. API error messages
|
|
62
|
+
contain the HTTP status rather than response bodies. Client inspection redacts
|
|
63
|
+
the API key; application code should also avoid logging requests or payloads.
|
|
64
|
+
|
|
65
|
+
Ruby argument/JSON serialization errors propagate to the caller before network
|
|
66
|
+
I/O. Use a separate client for each account's key. In Rails, call this client
|
|
67
|
+
from your application service or job; it does not change `ActionMailer` settings.
|
|
68
|
+
|
|
69
|
+
## Test and package
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
bundle install
|
|
73
|
+
bundle exec ruby -Ilib -e 'Dir["test/*_test.rb"].sort.each { |file| require_relative file }'
|
|
74
|
+
gem build mailchannels-email-api.gemspec
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Unit tests replace the HTTP session; six native transport tests use a local TLS server with
|
|
78
|
+
synthetic credentials and messages. They exercise the real Net::HTTP transport, preserve API
|
|
79
|
+
hostname verification, and route only the expected API socket to loopback. Native tests cover
|
|
80
|
+
empty 202, dry-run 200, untrusted CA, hostname mismatch, redirects, HTTP errors and disconnect after
|
|
81
|
+
receipt. No real API request or email is sent. These checks do not replace separately authorized provider validation. See [RELEASING.md](RELEASING.md) for the release gates.
|
|
82
|
+
Bundler may compile the `json` dependency; development containers need a C
|
|
83
|
+
compiler and make (for example, Debian's `build-essential` package).
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'openssl'
|
|
6
|
+
|
|
7
|
+
module MailChannels
|
|
8
|
+
# Direct HTTPS access to the MailChannels Email API send endpoint.
|
|
9
|
+
module EmailAPI
|
|
10
|
+
VERSION = '0.1.0'
|
|
11
|
+
|
|
12
|
+
class Error < StandardError; end
|
|
13
|
+
class ConfigurationError < Error; end
|
|
14
|
+
class TransportError < Error; end
|
|
15
|
+
|
|
16
|
+
# HTTP errors deliberately omit response bodies and credentials.
|
|
17
|
+
class APIError < Error
|
|
18
|
+
attr_reader :status
|
|
19
|
+
|
|
20
|
+
def initialize(status)
|
|
21
|
+
@status = status
|
|
22
|
+
super("MailChannels request failed (HTTP #{status})")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Acceptance is not delivery. A validation result never represents a send.
|
|
27
|
+
class Result
|
|
28
|
+
attr_reader :status
|
|
29
|
+
|
|
30
|
+
def initialize(status, dry_run)
|
|
31
|
+
@status = status
|
|
32
|
+
@dry_run = dry_run
|
|
33
|
+
freeze
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def validated?
|
|
37
|
+
@dry_run && status == 200
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def accepted?
|
|
41
|
+
!@dry_run && status == 202
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class Client
|
|
46
|
+
HOST = 'api.mailchannels.net'
|
|
47
|
+
PATH = '/tx/v1/send'
|
|
48
|
+
|
|
49
|
+
# Credentials are copied so caller mutation cannot alter this client.
|
|
50
|
+
def initialize(api_key: ENV['MAILCHANNELS_API_KEY'])
|
|
51
|
+
unless api_key.is_a?(String) && !api_key.strip.empty? && !api_key.match?(/[\r\n]/)
|
|
52
|
+
raise ConfigurationError, 'Set MAILCHANNELS_API_KEY or pass a nonempty api_key'
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
@api_key = api_key.dup.freeze
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def inspect
|
|
59
|
+
"#<#{self.class} api_key=[REDACTED]>"
|
|
60
|
+
end
|
|
61
|
+
alias to_s inspect
|
|
62
|
+
|
|
63
|
+
# Accepts the API's full JSON payload: personalizations, content,
|
|
64
|
+
# attachments, headers and other documented send fields pass through.
|
|
65
|
+
# No redirects or retries are performed, including on ambiguous failures.
|
|
66
|
+
def send_email(payload, dry_run: false)
|
|
67
|
+
raise ArgumentError, 'payload must be a Hash' unless payload.is_a?(Hash)
|
|
68
|
+
unless dry_run.equal?(true) || dry_run.equal?(false)
|
|
69
|
+
raise ArgumentError, 'dry_run must be true or false'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
request = Net::HTTP::Post.new(dry_run ? "#{PATH}?dry-run=true" : PATH)
|
|
73
|
+
request['Content-Type'] = 'application/json'
|
|
74
|
+
request['Accept'] = 'application/json'
|
|
75
|
+
request['X-Api-Key'] = @api_key
|
|
76
|
+
request['User-Agent'] = "mailchannels-email-api-ruby/#{VERSION}"
|
|
77
|
+
request.body = JSON.generate(payload)
|
|
78
|
+
|
|
79
|
+
http = Net::HTTP.new(HOST, 443, nil)
|
|
80
|
+
http.use_ssl = true
|
|
81
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
82
|
+
http.open_timeout = 10
|
|
83
|
+
http.read_timeout = 30
|
|
84
|
+
http.write_timeout = 30
|
|
85
|
+
http.max_retries = 0
|
|
86
|
+
response = http.start { |connection| connection.request(request) }
|
|
87
|
+
status = response.code.to_i
|
|
88
|
+
expected_status = dry_run ? 200 : 202
|
|
89
|
+
raise APIError, status unless status == expected_status
|
|
90
|
+
|
|
91
|
+
Result.new(status, dry_run)
|
|
92
|
+
rescue Timeout::Error, SocketError, SystemCallError, IOError,
|
|
93
|
+
OpenSSL::SSL::SSLError, Net::HTTPBadResponse, Net::ProtocolError
|
|
94
|
+
raise TransportError,
|
|
95
|
+
'MailChannels transport failed; delivery status may be unknown. No retry was attempted.',
|
|
96
|
+
cause: nil
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def validate_email(payload)
|
|
100
|
+
send_email(payload, dry_run: true)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: mailchannels-email-api
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- MailChannels Corporation
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: json
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.6'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '3'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '2.6'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '3'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: net-http
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '0.4'
|
|
39
|
+
- - "<"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '1'
|
|
42
|
+
type: :runtime
|
|
43
|
+
prerelease: false
|
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '0.4'
|
|
49
|
+
- - "<"
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '1'
|
|
52
|
+
description: Direct HTTPS client with API-key authentication, dry-run validation,
|
|
53
|
+
explicit acceptance status, bounded I/O timeouts, and no automatic retries.
|
|
54
|
+
email:
|
|
55
|
+
- dev@mailchannels.com
|
|
56
|
+
executables: []
|
|
57
|
+
extensions: []
|
|
58
|
+
extra_rdoc_files: []
|
|
59
|
+
files:
|
|
60
|
+
- LICENSE
|
|
61
|
+
- README.md
|
|
62
|
+
- lib/mailchannels/email_api.rb
|
|
63
|
+
homepage: https://www.mailchannels.com/email-api/
|
|
64
|
+
licenses:
|
|
65
|
+
- MIT
|
|
66
|
+
metadata:
|
|
67
|
+
source_code_uri: https://github.com/mailchannels/mailchannels-email-api-ruby
|
|
68
|
+
bug_tracker_uri: https://github.com/mailchannels/mailchannels-email-api-ruby/issues
|
|
69
|
+
documentation_uri: https://docs.mailchannels.com/email-api/overview
|
|
70
|
+
allowed_push_host: https://rubygems.org
|
|
71
|
+
rubygems_mfa_required: 'true'
|
|
72
|
+
rdoc_options: []
|
|
73
|
+
require_paths:
|
|
74
|
+
- lib
|
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
76
|
+
requirements:
|
|
77
|
+
- - ">="
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: '3.2'
|
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
81
|
+
requirements:
|
|
82
|
+
- - ">="
|
|
83
|
+
- !ruby/object:Gem::Version
|
|
84
|
+
version: '0'
|
|
85
|
+
requirements: []
|
|
86
|
+
rubygems_version: 3.6.9
|
|
87
|
+
specification_version: 4
|
|
88
|
+
summary: Send and validate transactional email through the MailChannels Email API.
|
|
89
|
+
test_files: []
|