ovh-http2sms 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.
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ovh
4
+ module Http2sms
5
+ # Validators for SMS parameters
6
+ #
7
+ # Provides validation for all SMS parameters before sending to ensure
8
+ # they meet OVH API requirements.
9
+ module Validators
10
+ # Maximum tag length allowed by OVH API
11
+ MAX_TAG_LENGTH = 20
12
+
13
+ # Valid SMS class values
14
+ VALID_SMS_CLASSES = (0..3).to_a.freeze
15
+
16
+ # Valid smsCoding values
17
+ VALID_SMS_CODINGS = [1, 2].freeze
18
+
19
+ # Deferred date format: hhmmddMMYYYY
20
+ DEFERRED_FORMAT_PATTERN = /\A\d{12}\z/
21
+
22
+ class << self
23
+ # Validate all parameters for an SMS delivery
24
+ #
25
+ # @param params [Hash] Parameters to validate
26
+ # @option params [String, Array<String>] :to Recipient phone number(s)
27
+ # @option params [String] :message SMS content
28
+ # @option params [String] :sender Sender name (optional)
29
+ # @option params [Time, String] :deferred Scheduled send time (optional)
30
+ # @option params [String] :tag Custom tag (optional, max 20 chars)
31
+ # @option params [Integer] :sms_class SMS class 0-3 (optional)
32
+ # @option params [Integer] :sms_coding Encoding 1=7bit, 2=Unicode (optional)
33
+ # @option params [Boolean] :no_stop Disable STOP clause (optional)
34
+ # @option params [Boolean] :sender_for_response Enable reply capability (optional)
35
+ # @raise [ValidationError, PhoneNumberError, MessageLengthError] if validation fails
36
+ # @return [void]
37
+ def validate!(params)
38
+ validate_required_params!(params)
39
+ validate_phone_numbers!(params[:to])
40
+ validate_message!(params[:message], no_stop: params[:no_stop])
41
+ validate_tag!(params[:tag]) if params[:tag]
42
+ validate_deferred!(params[:deferred]) if params[:deferred]
43
+ validate_sms_class!(params[:sms_class]) if params[:sms_class]
44
+ validate_sms_coding!(params[:sms_coding]) if params[:sms_coding]
45
+ validate_sender_for_response!(params) if params[:sender_for_response]
46
+ end
47
+
48
+ # Validate presence of required parameters
49
+ #
50
+ # @param params [Hash] Parameters to validate
51
+ # @raise [ValidationError] if required params are missing
52
+ def validate_required_params!(params)
53
+ missing = []
54
+ missing << "to" if params[:to].nil? || params[:to].to_s.empty?
55
+ missing << "message" if params[:message].nil? || params[:message].to_s.empty?
56
+
57
+ return if missing.empty?
58
+
59
+ raise ValidationError, "Missing required parameters: #{missing.join(", ")}"
60
+ end
61
+
62
+ # Validate phone number(s)
63
+ #
64
+ # @param phones [String, Array<String>] Phone number(s) to validate
65
+ # @raise [PhoneNumberError] if any phone number is invalid
66
+ def validate_phone_numbers!(phones)
67
+ phone_list = phones.is_a?(Array) ? phones : [phones]
68
+ phone_list.each do |phone|
69
+ PhoneNumber.validate!(PhoneNumber.format(phone))
70
+ end
71
+ end
72
+
73
+ # Validate message content and length
74
+ #
75
+ # @param message [String] Message content
76
+ # @param no_stop [Boolean] Whether STOP clause is disabled
77
+ # @raise [MessageLengthError] if message exceeds limits (when raise_on_length_error is true)
78
+ def validate_message!(message, no_stop: false)
79
+ info = GsmEncoding.message_info(message, commercial: !no_stop)
80
+ log_unicode_warning(info)
81
+ validate_message_length!(info)
82
+ end
83
+
84
+ def log_unicode_warning(info)
85
+ logger = Ovh::Http2sms.configuration.logger
86
+ return unless info[:encoding] == :unicode && logger
87
+
88
+ non_gsm = info[:non_gsm_chars].join(", ")
89
+ logger.warn(
90
+ "[OVH HTTP2SMS] Message requires Unicode encoding due to characters: #{non_gsm}. " \
91
+ "This reduces maximum SMS length from 160 to 70 characters."
92
+ )
93
+ end
94
+
95
+ def validate_message_length!(info)
96
+ return unless info[:sms_count] > 10
97
+
98
+ if Ovh::Http2sms.configuration.raise_on_length_error
99
+ raise_length_error(info)
100
+ else
101
+ log_length_warning(info)
102
+ end
103
+ end
104
+
105
+ def raise_length_error(info)
106
+ raise MessageLengthError.new(
107
+ "Message is very long and will be sent as #{info[:sms_count]} SMS segments. " \
108
+ "Current length: #{info[:characters]} characters (#{info[:encoding]} encoding).",
109
+ encoding: info[:encoding],
110
+ length: info[:characters],
111
+ max_length: info[:max_single_sms]
112
+ )
113
+ end
114
+
115
+ def log_length_warning(info)
116
+ logger = Ovh::Http2sms.configuration.logger
117
+ return unless logger
118
+
119
+ logger.warn(
120
+ "[OVH HTTP2SMS] Message will be sent as #{info[:sms_count]} SMS segments " \
121
+ "(#{info[:characters]} characters, #{info[:encoding]} encoding). This may incur additional charges."
122
+ )
123
+ end
124
+
125
+ # Validate tag length
126
+ #
127
+ # @param tag [String] Tag to validate
128
+ # @raise [ValidationError] if tag exceeds maximum length
129
+ def validate_tag!(tag)
130
+ return if tag.to_s.length <= MAX_TAG_LENGTH
131
+
132
+ raise ValidationError,
133
+ "Tag exceeds maximum length of #{MAX_TAG_LENGTH} characters (got #{tag.length})"
134
+ end
135
+
136
+ # Validate deferred date format
137
+ #
138
+ # @param deferred [Time, String] Deferred send time
139
+ # @raise [ValidationError] if format is invalid
140
+ def validate_deferred!(deferred)
141
+ return if deferred.is_a?(Time) || deferred.is_a?(DateTime)
142
+
143
+ deferred_str = deferred.to_s
144
+ return if deferred_str.match?(DEFERRED_FORMAT_PATTERN)
145
+
146
+ raise ValidationError,
147
+ "Invalid deferred format: '#{deferred}'. " \
148
+ "Expected hhmmddMMYYYY format (e.g., 125025112024 for 25/11/2024 at 12:50) " \
149
+ "or a Ruby Time/DateTime object."
150
+ end
151
+
152
+ # Validate SMS class
153
+ #
154
+ # @param sms_class [Integer] SMS class value
155
+ # @raise [ValidationError] if class is invalid
156
+ def validate_sms_class!(sms_class)
157
+ return if VALID_SMS_CLASSES.include?(sms_class.to_i)
158
+
159
+ raise ValidationError,
160
+ "Invalid SMS class: #{sms_class}. Must be 0, 1, 2, or 3."
161
+ end
162
+
163
+ # Validate SMS coding
164
+ #
165
+ # @param sms_coding [Integer] SMS coding value
166
+ # @raise [ValidationError] if coding is invalid
167
+ def validate_sms_coding!(sms_coding)
168
+ return if VALID_SMS_CODINGS.include?(sms_coding.to_i)
169
+
170
+ raise ValidationError,
171
+ "Invalid SMS coding: #{sms_coding}. Must be 1 (7-bit) or 2 (Unicode)."
172
+ end
173
+
174
+ # Validate sender_for_response compatibility
175
+ #
176
+ # @param params [Hash] Full parameters hash
177
+ # @return [void]
178
+ def validate_sender_for_response!(params)
179
+ return unless params[:sender_for_response] && params[:sender]
180
+
181
+ # Log warning but don't raise error
182
+ Ovh::Http2sms.configuration.logger&.warn(
183
+ "[OVH HTTP2SMS] senderForResponse is enabled but a sender is also specified. " \
184
+ "The 'from' parameter will be ignored. Leave sender empty when using senderForResponse."
185
+ )
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ovh
4
+ module Http2sms
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "http2sms/version"
4
+ require_relative "http2sms/errors"
5
+ require_relative "http2sms/configuration"
6
+ require_relative "http2sms/gsm_encoding"
7
+ require_relative "http2sms/phone_number"
8
+ require_relative "http2sms/response"
9
+ require_relative "http2sms/validators"
10
+ require_relative "http2sms/client"
11
+
12
+ # Load Rails integration if Rails is defined
13
+ require_relative "http2sms/railtie" if defined?(Rails::Railtie)
14
+
15
+ module Ovh
16
+ # OVH HTTP2SMS Ruby client
17
+ #
18
+ # Send SMS via OVH's http2sms API using simple HTTP GET requests.
19
+ # Supports single and bulk sending, scheduled messages, GSM/Unicode encoding,
20
+ # phone number formatting, and Rails integration.
21
+ #
22
+ # @example Configuration
23
+ # Ovh::Http2sms.configure do |config|
24
+ # config.account = "sms-xx11111-1"
25
+ # config.login = "user"
26
+ # config.password = "secret"
27
+ # end
28
+ #
29
+ # @example Simple send
30
+ # response = Ovh::Http2sms.deliver(to: "33601020304", message: "Hello!")
31
+ # response.success? # => true
32
+ # response.sms_ids # => ["123456789"]
33
+ #
34
+ # @example With options
35
+ # Ovh::Http2sms.deliver(
36
+ # to: ["33601020304", "33602030405"],
37
+ # message: "Meeting reminder",
38
+ # sender: "MyCompany",
39
+ # deferred: 1.hour.from_now,
40
+ # tag: "meeting-reminders"
41
+ # )
42
+ #
43
+ # @example Check message length
44
+ # Ovh::Http2sms.message_info("Hello!")
45
+ # # => { characters: 6, encoding: :gsm, sms_count: 1, remaining: 143 }
46
+ #
47
+ module Http2sms
48
+ class << self
49
+ # @return [Configuration] Current configuration
50
+ attr_writer :configuration
51
+
52
+ # Get the current configuration
53
+ #
54
+ # @return [Configuration] Configuration instance
55
+ def configuration
56
+ @configuration ||= Configuration.new
57
+ end
58
+
59
+ # Configure the gem
60
+ #
61
+ # @yield [Configuration] Configuration block
62
+ # @return [Configuration] The configuration object
63
+ #
64
+ # @example
65
+ # Ovh::Http2sms.configure do |config|
66
+ # config.account = "sms-xx11111-1"
67
+ # config.login = "user"
68
+ # config.password = "secret"
69
+ # config.default_sender = "MyApp"
70
+ # config.timeout = 30
71
+ # end
72
+ def configure
73
+ yield(configuration)
74
+ configuration
75
+ end
76
+
77
+ # Reset configuration to defaults
78
+ #
79
+ # @return [Configuration] New configuration with defaults
80
+ def reset_configuration!
81
+ @configuration = Configuration.new
82
+ end
83
+
84
+ # Send an SMS message using the global configuration
85
+ #
86
+ # @param (see Client#deliver)
87
+ # @return (see Client#deliver)
88
+ # @raise (see Client#deliver)
89
+ #
90
+ # @example
91
+ # Ovh::Http2sms.deliver(to: "33601020304", message: "Hello!")
92
+ def deliver(**options)
93
+ client.deliver(**options)
94
+ end
95
+
96
+ # Get a new client instance with optional configuration overrides
97
+ #
98
+ # @param options [Hash] Configuration options to override
99
+ # @return [Client] New client instance
100
+ #
101
+ # @example Use different credentials
102
+ # client = Ovh::Http2sms.client(account: "sms-other-1")
103
+ # client.deliver(to: "33601020304", message: "Hello!")
104
+ def client(**options)
105
+ Client.new(**options)
106
+ end
107
+
108
+ # Get message information (character count, encoding, SMS count)
109
+ #
110
+ # @param message [String] Message to analyze
111
+ # @param commercial [Boolean] Whether this is a commercial SMS (default: true)
112
+ # @return [Hash] Message information
113
+ #
114
+ # @example
115
+ # Ovh::Http2sms.message_info("Hello!")
116
+ # # => { characters: 6, encoding: :gsm, sms_count: 1, remaining: 143, ... }
117
+ #
118
+ # @example Non-commercial SMS
119
+ # Ovh::Http2sms.message_info("Hello!", commercial: false)
120
+ # # => { characters: 6, encoding: :gsm, sms_count: 1, remaining: 154, ... }
121
+ def message_info(message, commercial: true)
122
+ GsmEncoding.message_info(message, commercial: commercial)
123
+ end
124
+
125
+ # Check if a message uses only GSM characters
126
+ #
127
+ # @param message [String] Message to check
128
+ # @return [Boolean] true if all characters are GSM compatible
129
+ #
130
+ # @example
131
+ # Ovh::Http2sms.gsm_compatible?("Hello!") # => true
132
+ # Ovh::Http2sms.gsm_compatible?("Привет") # => false
133
+ def gsm_compatible?(message)
134
+ GsmEncoding.gsm_compatible?(message)
135
+ end
136
+
137
+ # Format a phone number to international format
138
+ #
139
+ # @param phone [String] Phone number in local or international format
140
+ # @param country_code [String] Country code for local numbers (default: from config)
141
+ # @return [String] Phone number in OVH format (00 prefix)
142
+ #
143
+ # @example
144
+ # Ovh::Http2sms.format_phone("0601020304") # => "0033601020304"
145
+ # Ovh::Http2sms.format_phone("+33601020304") # => "0033601020304"
146
+ def format_phone(phone, country_code: nil)
147
+ PhoneNumber.format(phone, country_code: country_code)
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/ovh/http2sms/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "ovh-http2sms"
7
+ spec.version = Ovh::Http2sms::VERSION
8
+ spec.authors = ["François Kiene"]
9
+
10
+ spec.summary = "Ruby gem to send SMS via OVH's http2sms API"
11
+ spec.description = "A production-ready Ruby gem that wraps OVH's http2sms API to send SMS via simple HTTP GET requests. " \
12
+ "Supports single and bulk sending, scheduled messages, GSM/Unicode encoding detection, " \
13
+ "phone number formatting, and Rails integration."
14
+ spec.homepage = "https://github.com/fkiene/ovh-http2sms"
15
+ spec.license = "MIT"
16
+ spec.required_ruby_version = ">= 3.0.0"
17
+
18
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/fkiene/ovh-http2sms"
21
+ spec.metadata["changelog_uri"] = "https://github.com/fkiene/ovh-http2sms/blob/main/CHANGELOG.md"
22
+ spec.metadata["documentation_uri"] = "https://rubydoc.info/gems/ovh-http2sms"
23
+ spec.metadata["rubygems_mfa_required"] = "true"
24
+
25
+ # Specify which files should be added to the gem when it is released.
26
+ spec.files = Dir.chdir(__dir__) do
27
+ `git ls-files -z`.split("\x0").reject do |f|
28
+ f.start_with?(*%w[bin/ spec/ .git .github .rspec .rubocop])
29
+ end
30
+ end
31
+ spec.bindir = "exe"
32
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
33
+ spec.require_paths = ["lib"]
34
+
35
+ # Runtime dependencies
36
+ spec.add_dependency "faraday", ">= 1.0", "< 3.0"
37
+ spec.add_dependency "gsm_encoder", "~> 0.1.7"
38
+
39
+ # Development dependencies
40
+ spec.add_development_dependency "rake", "~> 13.0"
41
+ spec.add_development_dependency "rspec", "~> 3.12"
42
+ spec.add_development_dependency "rubocop", "~> 1.50"
43
+ spec.add_development_dependency "rubocop-rspec", "~> 2.20"
44
+ spec.add_development_dependency "simplecov", "~> 0.22"
45
+ spec.add_development_dependency "webmock", "~> 3.18"
46
+ spec.add_development_dependency "yard", "~> 0.9"
47
+ end
@@ -0,0 +1,6 @@
1
+ module Ovh
2
+ module Http2sms
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,205 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ovh-http2sms
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - François Kiene
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-01-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '1.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: gsm_encoder
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.1.7
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 0.1.7
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '13.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '13.0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rspec
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3.12'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3.12'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rubocop
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '1.50'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '1.50'
89
+ - !ruby/object:Gem::Dependency
90
+ name: rubocop-rspec
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '2.20'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '2.20'
103
+ - !ruby/object:Gem::Dependency
104
+ name: simplecov
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.22'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '0.22'
117
+ - !ruby/object:Gem::Dependency
118
+ name: webmock
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '3.18'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '3.18'
131
+ - !ruby/object:Gem::Dependency
132
+ name: yard
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '0.9'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '0.9'
145
+ description: A production-ready Ruby gem that wraps OVH's http2sms API to send SMS
146
+ via simple HTTP GET requests. Supports single and bulk sending, scheduled messages,
147
+ GSM/Unicode encoding detection, phone number formatting, and Rails integration.
148
+ email:
149
+ executables: []
150
+ extensions: []
151
+ extra_rdoc_files: []
152
+ files:
153
+ - ".dockerignore"
154
+ - ".env.example"
155
+ - CHANGELOG.md
156
+ - Dockerfile
157
+ - Gemfile
158
+ - LICENSE.txt
159
+ - README.md
160
+ - Rakefile
161
+ - docker-compose.yml
162
+ - lib/generators/ovh/http2sms/install_generator.rb
163
+ - lib/generators/ovh/http2sms/templates/initializer.rb
164
+ - lib/ovh/http2sms.rb
165
+ - lib/ovh/http2sms/client.rb
166
+ - lib/ovh/http2sms/configuration.rb
167
+ - lib/ovh/http2sms/errors.rb
168
+ - lib/ovh/http2sms/gsm_encoding.rb
169
+ - lib/ovh/http2sms/phone_number.rb
170
+ - lib/ovh/http2sms/railtie.rb
171
+ - lib/ovh/http2sms/response.rb
172
+ - lib/ovh/http2sms/validators.rb
173
+ - lib/ovh/http2sms/version.rb
174
+ - ovh-http2sms.gemspec
175
+ - sig/ovh/http2sms.rbs
176
+ homepage: https://github.com/fkiene/ovh-http2sms
177
+ licenses:
178
+ - MIT
179
+ metadata:
180
+ allowed_push_host: https://rubygems.org
181
+ homepage_uri: https://github.com/fkiene/ovh-http2sms
182
+ source_code_uri: https://github.com/fkiene/ovh-http2sms
183
+ changelog_uri: https://github.com/fkiene/ovh-http2sms/blob/main/CHANGELOG.md
184
+ documentation_uri: https://rubydoc.info/gems/ovh-http2sms
185
+ rubygems_mfa_required: 'true'
186
+ post_install_message:
187
+ rdoc_options: []
188
+ require_paths:
189
+ - lib
190
+ required_ruby_version: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: 3.0.0
195
+ required_rubygems_version: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - ">="
198
+ - !ruby/object:Gem::Version
199
+ version: '0'
200
+ requirements: []
201
+ rubygems_version: 3.5.22
202
+ signing_key:
203
+ specification_version: 4
204
+ summary: Ruby gem to send SMS via OVH's http2sms API
205
+ test_files: []