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.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +83 -0
  4. data/lib/mailchannels/email_api.rb +104 -0
  5. 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: []