jetemail 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: 5689f2bfdd4487a75d88fb2bf22b02abaf3d99f318a268093544cd9ae58fc3ee
4
+ data.tar.gz: 96b236ee47f7a9724d2bcd610c626c73309e715aa9b0b57766fe93be3869c3c3
5
+ SHA512:
6
+ metadata.gz: f1bcb138b5ffc80f2009b362f5993375d47e1bbe51bb8b65641425c1e40029b1b398a2be084d04e0be213107c5f158247ba0cb1190dbb27fbca483c7b88bf6c7
7
+ data.tar.gz: 21db98dd2d350d67dd4b0d56d27390b238b49a8c731fb0d4e779c9782f77dd65f338269c5510ef3de22ca1b5fde6d8fa58fa34b1c411dc7d40106c8205735697
data/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Initial release
6
+ - Send single emails via `JetEmail::Emails.send`
7
+ - Send batch emails via `JetEmail::Batch.send`
8
+ - Webhooks CRUD: list, get, create, update, remove, query, replay
9
+ - Webhook signature verification via `JetEmail::Webhooks.verify`
10
+ - Rails ActionMailer integration
11
+ - Error handling with typed exceptions and rate limit support
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 JetEmail
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,204 @@
1
+ # JetEmail Ruby SDK
2
+
3
+ Ruby SDK for the [JetEmail](https://jetemail.com) transactional email service.
4
+
5
+ ## Installation
6
+
7
+ Add to your Gemfile:
8
+
9
+ ```ruby
10
+ gem "jetemail"
11
+ ```
12
+
13
+ Then run `bundle install`. Or install directly:
14
+
15
+ ```
16
+ gem install jetemail
17
+ ```
18
+
19
+ ## Configuration
20
+
21
+ ```ruby
22
+ require "jetemail"
23
+
24
+ JetEmail.configure do |config|
25
+ config.api_key = "transactional_your_key_here"
26
+ end
27
+ ```
28
+
29
+ Or set the `JETEMAIL_API_KEY` environment variable and configure in an initializer.
30
+
31
+ ## Sending Emails
32
+
33
+ ### Single Email
34
+
35
+ ```ruby
36
+ JetEmail::Emails.send({
37
+ from: "App <hello@yourdomain.com>",
38
+ to: "user@example.com",
39
+ subject: "Welcome!",
40
+ html: "<h1>Hello</h1><p>Welcome to our app.</p>"
41
+ })
42
+ # => { id: "msg_123", response: "Queued" }
43
+ ```
44
+
45
+ ### With Attachments
46
+
47
+ ```ruby
48
+ JetEmail::Emails.send({
49
+ from: "App <hello@yourdomain.com>",
50
+ to: "user@example.com",
51
+ subject: "Your invoice",
52
+ html: "<p>Please find your invoice attached.</p>",
53
+ attachments: [
54
+ { filename: "invoice.pdf", data: Base64.strict_encode64(File.read("invoice.pdf")) }
55
+ ]
56
+ })
57
+ ```
58
+
59
+ ### All Options
60
+
61
+ ```ruby
62
+ JetEmail::Emails.send({
63
+ from: "App <hello@yourdomain.com>",
64
+ to: ["user1@example.com", "user2@example.com"], # max 50
65
+ subject: "Hello",
66
+ html: "<h1>Hello</h1>",
67
+ text: "Hello", # plain text fallback
68
+ cc: "cc@example.com", # string or array, max 50
69
+ bcc: ["bcc1@example.com"], # string or array, max 50
70
+ reply_to: "reply@example.com", # string or array, max 50
71
+ headers: { "X-Custom-Header" => "value" },
72
+ attachments: [{ filename: "file.txt", data: "base64..." }],
73
+ eu: true # EU-only delivery
74
+ })
75
+ ```
76
+
77
+ ### Batch Email
78
+
79
+ Send up to 100 emails in a single request:
80
+
81
+ ```ruby
82
+ JetEmail::Batch.send([
83
+ {
84
+ from: "App <hello@yourdomain.com>",
85
+ to: "user1@example.com",
86
+ subject: "Hello User 1",
87
+ html: "<p>Hi User 1</p>"
88
+ },
89
+ {
90
+ from: "App <hello@yourdomain.com>",
91
+ to: "user2@example.com",
92
+ subject: "Hello User 2",
93
+ html: "<p>Hi User 2</p>"
94
+ }
95
+ ])
96
+ # => { summary: { total: 2, successful: 2, failed: 0 }, results: [...] }
97
+ ```
98
+
99
+ ## Webhooks
100
+
101
+ ### CRUD Operations
102
+
103
+ ```ruby
104
+ # List all webhooks
105
+ JetEmail::Webhooks.list
106
+
107
+ # Get a webhook
108
+ JetEmail::Webhooks.get("webhook_uuid")
109
+
110
+ # Create a webhook
111
+ JetEmail::Webhooks.create({
112
+ name: "My Webhook",
113
+ url: "https://example.com/webhook",
114
+ events: ["outbound.delivered", "outbound.bounced"]
115
+ })
116
+
117
+ # Update a webhook
118
+ JetEmail::Webhooks.update({ uuid: "webhook_uuid", name: "Updated Name" })
119
+
120
+ # Delete a webhook
121
+ JetEmail::Webhooks.remove("webhook_uuid")
122
+
123
+ # Query webhook events
124
+ JetEmail::Webhooks.query({ uuid: "webhook_uuid", event_type: "outbound.delivered" })
125
+
126
+ # Replay a webhook event
127
+ JetEmail::Webhooks.replay({ event_id: "event_123" })
128
+ ```
129
+
130
+ ### Webhook Event Types
131
+
132
+ **Outbound:** `outbound.queued`, `outbound.delivered`, `outbound.bounced`, `outbound.rejected`, `outbound.deferred`, `outbound.spam`, `outbound.dropped`, `outbound.virus`, `outbound.opened`, `outbound.clicked`, `outbound.complaint`
133
+
134
+ ### Signature Verification
135
+
136
+ Verify incoming webhook requests using the `X-Webhook-Signature`, `X-Webhook-Timestamp`, and your webhook secret:
137
+
138
+ ```ruby
139
+ JetEmail::Webhooks.verify(
140
+ payload: request.raw_post,
141
+ signature: request.headers["X-Webhook-Signature"],
142
+ timestamp: request.headers["X-Webhook-Timestamp"],
143
+ secret: ENV["JETEMAIL_WEBHOOK_SECRET"]
144
+ )
145
+ # => true (or raises JetEmail::Error)
146
+ ```
147
+
148
+ ## Rails Integration
149
+
150
+ The gem integrates with Rails ActionMailer automatically.
151
+
152
+ ### Setup
153
+
154
+ In `config/environments/production.rb`:
155
+
156
+ ```ruby
157
+ config.action_mailer.delivery_method = :jetemail
158
+ ```
159
+
160
+ In an initializer (`config/initializers/jetemail.rb`):
161
+
162
+ ```ruby
163
+ JetEmail.configure do |config|
164
+ config.api_key = ENV["JETEMAIL_API_KEY"]
165
+ end
166
+ ```
167
+
168
+ ### Usage
169
+
170
+ Use ActionMailer as normal:
171
+
172
+ ```ruby
173
+ class UserMailer < ApplicationMailer
174
+ def welcome(user)
175
+ mail(
176
+ from: "App <hello@yourdomain.com>",
177
+ to: user.email,
178
+ subject: "Welcome!"
179
+ )
180
+ end
181
+ end
182
+ ```
183
+
184
+ ## Error Handling
185
+
186
+ ```ruby
187
+ begin
188
+ JetEmail::Emails.send({ from: "a@b.com", to: "c@d.com", subject: "Hi", text: "Hello" })
189
+ rescue JetEmail::Error::RateLimitError => e
190
+ puts "Rate limited. Retry after: #{e.retry_after}s"
191
+ rescue JetEmail::Error::InvalidRequestError => e
192
+ puts "Invalid request: #{e.message} (#{e.status_code})"
193
+ rescue JetEmail::Error::NotFoundError => e
194
+ puts "Not found: #{e.message}"
195
+ rescue JetEmail::Error::InternalServerError => e
196
+ puts "Server error: #{e.message}"
197
+ rescue JetEmail::Error => e
198
+ puts "Error: #{e.message} (#{e.status_code})"
199
+ end
200
+ ```
201
+
202
+ ## License
203
+
204
+ MIT
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JetEmail
4
+ module Batch
5
+ class << self
6
+ # Send a batch of up to 100 emails.
7
+ #
8
+ # @param emails [Array<Hash>] Array of email parameter hashes (same schema as Emails.send)
9
+ # @return [Hash] Response with :summary and :results keys
10
+ def send(emails = [])
11
+ path = "email-batch"
12
+ JetEmail::Request.new(path, { emails: emails }, "post").perform
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JetEmail
4
+ module Emails
5
+ class << self
6
+ # Send a single email.
7
+ #
8
+ # @param params [Hash] Email parameters
9
+ # @option params [String] :from Required. Sender in "Name <email>" format
10
+ # @option params [String, Array<String>] :to Required. Recipient(s), max 50
11
+ # @option params [String] :subject Required. Email subject
12
+ # @option params [String] :html HTML body (at least one of html/text required)
13
+ # @option params [String] :text Plain text body
14
+ # @option params [String, Array<String>] :cc CC recipient(s), max 50
15
+ # @option params [String, Array<String>] :bcc BCC recipient(s), max 50
16
+ # @option params [String, Array<String>] :reply_to Reply-to address(es), max 50
17
+ # @option params [Hash] :headers Custom email headers
18
+ # @option params [Array<Hash>] :attachments File attachments (base64-encoded)
19
+ # @option params [Boolean] :eu EU-only delivery
20
+ # @return [Hash] Response with :id and :response keys
21
+ def send(params)
22
+ path = "email"
23
+ JetEmail::Request.new(path, params, "post").perform
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JetEmail
4
+ class Error < StandardError
5
+ ClientError = Class.new(self)
6
+ ServerError = Class.new(self)
7
+ InternalServerError = Class.new(ServerError)
8
+ InvalidRequestError = Class.new(ClientError)
9
+
10
+ class RateLimitError < ClientError
11
+ attr_reader :retry_after
12
+
13
+ def initialize(msg, code = nil, headers = {})
14
+ super(msg, code, headers)
15
+ @retry_after = headers["retry-after"]&.to_i
16
+ end
17
+ end
18
+
19
+ NotFoundError = Class.new(ClientError)
20
+
21
+ ERRORS = {
22
+ 400 => InvalidRequestError,
23
+ 401 => InvalidRequestError,
24
+ 403 => InvalidRequestError,
25
+ 404 => NotFoundError,
26
+ 422 => InvalidRequestError,
27
+ 429 => RateLimitError,
28
+ 500 => InternalServerError
29
+ }.freeze
30
+
31
+ attr_reader :status_code, :headers
32
+
33
+ def initialize(msg, code = nil, headers = {})
34
+ super(msg)
35
+ @status_code = code
36
+ @headers = headers || {}
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jetemail"
4
+
5
+ module JetEmail
6
+ class Mailer
7
+ attr_accessor :config, :settings
8
+
9
+ IGNORED_HEADERS = %w[
10
+ from to cc bcc subject reply-to
11
+ mime-version content-type content-transfer-encoding
12
+ date message-id
13
+ ].freeze
14
+
15
+ def initialize(config)
16
+ @config = config
17
+ raise JetEmail::Error.new("API key is not set") unless JetEmail.api_key
18
+
19
+ @settings = { return_response: true }
20
+ end
21
+
22
+ def deliver!(mail)
23
+ params = build_params(mail)
24
+ resp = JetEmail::Emails.send(params)
25
+ mail.message_id = resp[:id] if resp[:id]
26
+ resp
27
+ end
28
+
29
+ private
30
+
31
+ def build_params(mail)
32
+ params = {
33
+ from: get_from(mail),
34
+ to: mail.to,
35
+ subject: mail.subject
36
+ }
37
+
38
+ params[:cc] = mail.cc if mail.cc&.any?
39
+ params[:bcc] = mail.bcc if mail.bcc&.any?
40
+ params[:reply_to] = mail.reply_to if mail.reply_to&.any?
41
+
42
+ params.merge!(get_contents(mail))
43
+
44
+ headers = get_headers(mail)
45
+ params[:headers] = headers unless headers.empty?
46
+
47
+ attachments = get_attachments(mail)
48
+ params[:attachments] = attachments if attachments.any?
49
+
50
+ params
51
+ end
52
+
53
+ def get_from(mail)
54
+ return mail.from.first if mail[:from].nil?
55
+
56
+ from = mail[:from].formatted
57
+ return from.first if from.is_a?(Array)
58
+
59
+ from.to_s
60
+ end
61
+
62
+ def get_contents(mail)
63
+ params = {}
64
+ case mail.mime_type
65
+ when "text/plain"
66
+ params[:text] = mail.body.decoded
67
+ when "text/html"
68
+ params[:html] = mail.body.decoded
69
+ when "multipart/alternative", "multipart/mixed", "multipart/related"
70
+ params[:text] = mail.text_part.decoded if mail.text_part
71
+ params[:html] = mail.html_part.decoded if mail.html_part
72
+ end
73
+ params
74
+ end
75
+
76
+ def get_headers(mail)
77
+ headers = {}
78
+ mail.header_fields.each do |field|
79
+ next if IGNORED_HEADERS.include?(field.name.downcase)
80
+
81
+ headers[field.name] = field.unparsed_value
82
+ end
83
+ headers
84
+ end
85
+
86
+ def get_attachments(mail)
87
+ mail.attachments.map do |part|
88
+ headers = part.respond_to?(:header) ? part.header : nil
89
+ filename = part.filename || "attachment"
90
+ {
91
+ filename: filename,
92
+ data: Base64.strict_encode64(part.body.decoded)
93
+ }
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jetemail"
4
+ require "jetemail/mailer"
5
+
6
+ module JetEmail
7
+ class Railtie < ::Rails::Railtie
8
+ ActiveSupport.on_load(:action_mailer) do
9
+ add_delivery_method :jetemail, JetEmail::Mailer
10
+ ActiveSupport.run_load_hooks(:jetemail_mailer, JetEmail::Mailer)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JetEmail
4
+ class Request
5
+ BASE_URL = ENV["JETEMAIL_BASE_URL"] || "https://api.jetemail.com"
6
+
7
+ attr_accessor :body, :verb
8
+
9
+ def initialize(path = "", body = {}, verb = "post")
10
+ raise JetEmail::Error.new("API key is not set") if JetEmail.api_key.nil?
11
+
12
+ api_key = JetEmail.api_key
13
+ api_key = api_key.call if api_key.is_a?(Proc)
14
+
15
+ @path = path
16
+ @body = body
17
+ @verb = verb
18
+ @headers = {
19
+ "Content-Type" => "application/json",
20
+ "Accept" => "application/json",
21
+ "User-Agent" => "jetemail-ruby:#{JetEmail::VERSION}",
22
+ "Authorization" => "Bearer #{api_key}"
23
+ }
24
+ end
25
+
26
+ def perform
27
+ options = build_options
28
+ resp = HTTParty.send(@verb.to_sym, "#{BASE_URL}/#{@path}", options)
29
+
30
+ data = parse_response(resp)
31
+ handle_error!(data, resp) if error_response?(resp)
32
+ data
33
+ end
34
+
35
+ private
36
+
37
+ def build_options
38
+ options = { headers: @headers }
39
+
40
+ if @verb.downcase == "get" && !@body.empty?
41
+ options[:query] = @body
42
+ elsif !@body.empty?
43
+ options[:body] = @body.to_json
44
+ end
45
+
46
+ options
47
+ end
48
+
49
+ def parse_response(resp)
50
+ return {} if resp.body.nil? || resp.body.empty?
51
+
52
+ JSON.parse(resp.body, symbolize_names: true)
53
+ rescue JSON::ParserError
54
+ raise JetEmail::Error::InternalServerError.new("Unexpected response from JetEmail API", resp.code)
55
+ end
56
+
57
+ def error_response?(resp)
58
+ resp.code >= 400
59
+ end
60
+
61
+ def handle_error!(data, resp)
62
+ raw_headers = resp.respond_to?(:headers) ? resp.headers.to_h : {}
63
+ headers = raw_headers.transform_values { |v| v.is_a?(Array) ? v.last : v }
64
+ message = data[:message] || data[:error] || "Request failed with status #{resp.code}"
65
+ error_class = JetEmail::Error::ERRORS[resp.code] || JetEmail::Error
66
+ raise error_class.new(message, resp.code, headers)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JetEmail
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
5
+ module JetEmail
6
+ WEBHOOK_TOLERANCE_SECONDS = 300
7
+
8
+ module Webhooks
9
+ class << self
10
+ # List all webhooks.
11
+ #
12
+ # @return [Hash] List of webhooks
13
+ def list
14
+ path = "webhooks"
15
+ JetEmail::Request.new(path, {}, "get").perform
16
+ end
17
+
18
+ # Get a single webhook.
19
+ #
20
+ # @param uuid [String] Webhook UUID
21
+ # @return [Hash] Webhook details
22
+ def get(uuid)
23
+ path = "webhooks/#{uuid}"
24
+ JetEmail::Request.new(path, {}, "get").perform
25
+ end
26
+
27
+ # Create a webhook.
28
+ #
29
+ # @param params [Hash] Webhook parameters
30
+ # @option params [String] :name Required. Webhook name
31
+ # @option params [String] :url Required. Webhook URL
32
+ # @option params [Array<String>] :events Required. Event types to subscribe to
33
+ # @return [Hash] Created webhook
34
+ def create(params)
35
+ path = "webhooks"
36
+ JetEmail::Request.new(path, params, "post").perform
37
+ end
38
+
39
+ # Update a webhook.
40
+ #
41
+ # @param params [Hash] Webhook parameters
42
+ # @option params [String] :uuid Required. Webhook UUID
43
+ # @return [Hash] Updated webhook
44
+ def update(params)
45
+ path = "webhooks"
46
+ JetEmail::Request.new(path, params, "patch").perform
47
+ end
48
+
49
+ # Delete a webhook.
50
+ #
51
+ # @param uuid [String] Webhook UUID
52
+ # @return [Hash] Deletion confirmation
53
+ def remove(uuid)
54
+ path = "webhooks/#{uuid}"
55
+ JetEmail::Request.new(path, {}, "delete").perform
56
+ end
57
+
58
+ # Query webhook events.
59
+ #
60
+ # @param params [Hash] Query filters
61
+ # @return [Hash] Matching webhook events
62
+ def query(params = {})
63
+ path = "webhooks/query"
64
+ JetEmail::Request.new(path, params, "post").perform
65
+ end
66
+
67
+ # Replay a webhook event.
68
+ #
69
+ # @param params [Hash] Replay parameters
70
+ # @option params [String] :event_id Event ID to replay
71
+ # @option params [String] :source_uid Source UID to replay
72
+ # @return [Hash] Replay confirmation
73
+ def replay(params)
74
+ path = "webhooks/replay"
75
+ JetEmail::Request.new(path, params, "post").perform
76
+ end
77
+
78
+ # Verify a webhook signature.
79
+ #
80
+ # @param payload [String] Raw request body
81
+ # @param signature [String] X-Webhook-Signature header value
82
+ # @param timestamp [String] X-Webhook-Timestamp header value
83
+ # @param secret [String] Your webhook secret
84
+ # @param tolerance [Integer] Max age in seconds (default: 300)
85
+ # @return [Boolean] true if valid
86
+ # @raise [JetEmail::Error] if verification fails
87
+ def verify(payload:, signature:, timestamp:, secret:, tolerance: WEBHOOK_TOLERANCE_SECONDS)
88
+ raise JetEmail::Error.new("Payload cannot be empty") if payload.nil? || payload.empty?
89
+ raise JetEmail::Error.new("Signature cannot be empty") if signature.nil? || signature.empty?
90
+ raise JetEmail::Error.new("Timestamp cannot be empty") if timestamp.nil? || timestamp.to_s.empty?
91
+ raise JetEmail::Error.new("Secret cannot be empty") if secret.nil? || secret.empty?
92
+
93
+ ts = timestamp.to_i
94
+ diff = (Time.now.to_i - ts).abs
95
+ if tolerance > 0 && diff > tolerance
96
+ raise JetEmail::Error.new("Webhook timestamp is outside tolerance (#{diff}s)")
97
+ end
98
+
99
+ expected = "sha256=" + OpenSSL::HMAC.hexdigest("SHA256", secret, payload)
100
+ unless secure_compare(expected, signature)
101
+ raise JetEmail::Error.new("Webhook signature verification failed")
102
+ end
103
+
104
+ true
105
+ end
106
+
107
+ private
108
+
109
+ def secure_compare(a, b)
110
+ return false if a.nil? || b.nil? || a.bytesize != b.bytesize
111
+
112
+ bytes_a = a.unpack("C*")
113
+ result = 0
114
+ b.each_byte.with_index { |byte, i| result |= byte ^ bytes_a[i] }
115
+ result.zero?
116
+ end
117
+ end
118
+ end
119
+ end
data/lib/jetemail.rb ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "httparty"
4
+ require "json"
5
+
6
+ require "jetemail/version"
7
+ require "jetemail/errors"
8
+ require "jetemail/request"
9
+ require "jetemail/emails"
10
+ require "jetemail/batch"
11
+ require "jetemail/webhooks"
12
+
13
+ require "jetemail/railtie" if defined?(Rails) && defined?(ActionMailer)
14
+
15
+ module JetEmail
16
+ class << self
17
+ attr_accessor :api_key
18
+
19
+ def configure
20
+ yield self if block_given?
21
+ true
22
+ end
23
+ alias_method :config, :configure
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jetemail
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - JetEmail
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-04-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httparty
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.21'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.21'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: webmock
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: Ruby SDK for the JetEmail transactional email service.
56
+ email: support@jetemail.com
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - CHANGELOG.md
62
+ - LICENSE
63
+ - README.md
64
+ - lib/jetemail.rb
65
+ - lib/jetemail/batch.rb
66
+ - lib/jetemail/emails.rb
67
+ - lib/jetemail/errors.rb
68
+ - lib/jetemail/mailer.rb
69
+ - lib/jetemail/railtie.rb
70
+ - lib/jetemail/request.rb
71
+ - lib/jetemail/version.rb
72
+ - lib/jetemail/webhooks.rb
73
+ homepage: https://github.com/jetemail/jetemail-ruby
74
+ licenses:
75
+ - MIT
76
+ metadata:
77
+ homepage_uri: https://github.com/jetemail/jetemail-ruby
78
+ source_code_uri: https://github.com/jetemail/jetemail-ruby
79
+ changelog_uri: https://github.com/jetemail/jetemail-ruby/blob/main/CHANGELOG.md
80
+ bug_tracker_uri: https://github.com/jetemail/jetemail-ruby/issues
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '2.6'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubygems_version: 3.0.3.1
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: The Ruby and Rails SDK for jetemail.com
100
+ test_files: []