Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
source 'https://rubygems.org'

group :development do
gem 'bundler', '>= 2.2'
gem'dotenv', '~> 2.7.0'
gem'pry', '~> 0.14.0'
gem'rails'
gem'rake', '~> 13.0.0'
gem'rspec', '~> 3.10.0'
gem'rubocop', '~> 1.11.0'
gem'rubocop-rspec', '~> 2.2.0'
gem'simplecov', '~> 0.21.0'
gem'vcr', '~> 6.0.0'
gem'webmock', '~> 3.12.0'
gem 'bundler', '>= 2.5'
gem 'dotenv', '~> 3.1'
gem 'pry', '~> 0.14.2'
gem 'rails'
gem 'rake', '~> 13.2'
gem 'rspec', '~> 3.13'
gem 'rubocop', '~> 1.69'
gem 'rubocop-rspec', '~> 3.2'
gem 'simplecov', '~> 0.22'
gem 'vcr', '~> 6.3'
gem 'webmock', '~> 3.24'
end

gemspec
85 changes: 82 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ImprovMX Ruby Gem

Ruby interface to connect to the ImprovMX API.

Currently still work in progress, it only contains the aliases endpoints at the moment.
Supports both aliases and rules endpoints.


Installation
Expand All @@ -17,16 +17,95 @@ Usage
-----
This is how you can use this gem

### Aliases

```ruby
require 'improvmx'

# Instantiate the Client with your API key and domain
# Instantiate the Client with your API key
client = Improvmx::Client.new 'your-api-key'

# List all the aliases
aliases = client.list_aliases('domain.com')

puts aliases['aliases']

# Create an alias
client.create_alias('hello', 'receiver@example.com', 'domain.com')

# Get a specific alias
alias_info = client.get_alias('hello', 'domain.com')

# Update an alias
client.update_alias('hello', 'new_receiver@example.com', 'domain.com')

# Delete an alias
client.delete_alias('hello', 'domain.com')
```

### Rules

Rules provide advanced email routing based on patterns, regular expressions, or conditions.

```ruby
require 'improvmx'

client = Improvmx::Client.new 'your-api-key'

# List all rules
rules = client.list_rules('domain.com')
puts rules['rules']

# Create an alias rule (matches specific alias)
rule = client.create_alias_rule('domain.com', 'support', 'support@company.com')
puts rule['id'] # => UUID of the created rule

# Create a regex rule (matches based on regex pattern)
rule = client.create_regex_rule(
'domain.com',
'.*important.*', # Regex pattern
['subject', 'body'], # Scopes to match
'urgent@company.com' # Forward destination
)

# Create a CEL rule (matches based on CEL expression)
rule = client.create_cel_rule(
'domain.com',
"subject.contains('invoice')", # CEL expression
'billing@company.com'
)

# Create a rule with custom rank and active state
rule = client.create_alias_rule(
'domain.com',
'priority',
'priority@company.com',
rank: 1.0, # Lower rank = higher priority
active: true
)

# Get a specific rule by ID
rule = client.get_rule('rule-uuid', 'domain.com')

# Update a rule's config
client.update_rule(
'rule-uuid',
'domain.com',
config: { alias: 'support', forward: 'newsupport@company.com' },
active: false
)

# Delete a rule
client.delete_rule('rule-uuid', 'domain.com')

# Delete all rules
client.delete_all_rules('domain.com')

# Bulk add multiple rules
rules = [
{ type: 'alias', config: { alias: 'info', forward: 'info@company.com' } },
{ type: 'alias', config: { alias: 'sales', forward: 'sales@company.com' } }
]
client.bulk_modify_rules('domain.com', rules, behavior: 'add')
```

Improvmx has a rate limit system, to handle this you can do
Expand Down
4 changes: 2 additions & 2 deletions improvmx.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ Gem::Specification.new do |spec|
spec.files = %w[LICENSE README.md improvmx.gemspec] + Dir['lib/**/*.rb']
spec.require_paths = %w[lib]

spec.required_ruby_version = '>= 2.4'
spec.add_dependency 'rest-client', '~> 2.0'
spec.required_ruby_version = '>= 3.0'
spec.add_dependency 'rest-client', '~> 2.1'
end
2 changes: 2 additions & 0 deletions lib/improvmx/client.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'improvmx/aliases'
require 'improvmx/rules'
require 'improvmx/smtp'
require 'improvmx/response'
require 'improvmx/utils'
Expand All @@ -7,6 +8,7 @@
module Improvmx
class Client
include Improvmx::Aliases
include Improvmx::Rules
include Improvmx::SMTP
include Improvmx::Utils

Expand Down
149 changes: 149 additions & 0 deletions lib/improvmx/rules.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
module Improvmx
# All rule related endpoints
module Rules
# List all rules for a domain
# @param domain [String] The domain name
# @param params [Hash] Optional parameters (search, page)
# @return [Hash] Response containing rules array
def list_rules(domain, params = {})
get("/domains/#{domain}/rules/", params).to_h
end

# Get a specific rule
# @param rule_id [String] The rule ID (UUID)
# @param domain [String] The domain name
# @return [Hash, nil] The rule object or nil if not found
def get_rule(rule_id, domain)
get("/domains/#{domain}/rules/#{rule_id}").to_h
rescue NotFoundError
nil
end

# Create an alias rule
# @param domain [String] The domain name
# @param alias_name [String] The alias (e.g., "richard")
# @param forward_to [String, Array] Destination email(s)
# @param rank [Float, nil] Optional rank for evaluation priority
# @param active [Boolean] Whether the rule is active (default: true)
# @param id [String, nil] Optional custom rule ID
# @return [Hash] The created rule
def create_alias_rule(domain, alias_name, forward_to, rank: nil, active: true, id: nil)
data = {
type: 'alias',
config: {
alias: alias_name,
forward: forward(forward_to)
},
active: active
}
data[:rank] = rank if rank
data[:id] = id if id

response = post("/domains/#{domain}/rules/", data)
response.to_h['rule']
end
Comment on lines +30 to +44
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter order is inconsistent with the existing codebase patterns. Looking at the aliases module, the pattern used is create_alias(alias_name, forward_to, domain) where the resource identifier comes first, then the data, then the domain. However, this method has create_alias_rule(domain, alias_name, forward_to, ...) with domain first. For consistency, consider reordering parameters to match the existing pattern: create_alias_rule(alias_name, forward_to, domain, rank: nil, active: true, id: nil).

Copilot uses AI. Check for mistakes.

# Create a regex rule
# @param domain [String] The domain name
# @param regex [String] The regular expression pattern
# @param scopes [Array<String>] Scopes to match (sender, recipient, subject, body)
# @param forward_to [String, Array] Destination email(s)
# @param rank [Float, nil] Optional rank for evaluation priority
# @param active [Boolean] Whether the rule is active (default: true)
# @param id [String, nil] Optional custom rule ID
# @return [Hash] The created rule
def create_regex_rule(domain, regex, scopes, forward_to, rank: nil, active: true, id: nil)
data = {
type: 'regex',
config: {
regex: regex,
scopes: scopes,
forward: forward(forward_to)
},
active: active
}
data[:rank] = rank if rank
data[:id] = id if id

response = post("/domains/#{domain}/rules/", data)
response.to_h['rule']
end
Comment on lines +55 to +70
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter order is inconsistent with the existing codebase patterns. Looking at the aliases module, the pattern used is create_alias(alias_name, forward_to, domain) where the resource identifier comes first, then the data, then the domain. However, this method has create_regex_rule(domain, regex, scopes, forward_to, ...) with domain first. For consistency, consider reordering parameters to match the existing pattern: create_regex_rule(regex, scopes, forward_to, domain, rank: nil, active: true, id: nil).

Copilot uses AI. Check for mistakes.

# Create a CEL rule
# @param domain [String] The domain name
# @param expression [String] The CEL expression
# @param forward_to [String, Array] Destination email(s)
# @param rank [Float, nil] Optional rank for evaluation priority
# @param active [Boolean] Whether the rule is active (default: true)
# @param id [String, nil] Optional custom rule ID
# @return [Hash] The created rule
def create_cel_rule(domain, expression, forward_to, rank: nil, active: true, id: nil)
data = {
type: 'cel',
config: {
expression: expression,
forward: forward(forward_to)
},
active: active
}
data[:rank] = rank if rank
data[:id] = id if id

response = post("/domains/#{domain}/rules/", data)
response.to_h['rule']
end
Comment on lines +80 to +94
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter order is inconsistent with the existing codebase patterns. Looking at the aliases module, the pattern used is create_alias(alias_name, forward_to, domain) where the resource identifier comes first, then the data, then the domain. However, this method has create_cel_rule(domain, expression, forward_to, ...) with domain first. For consistency, consider reordering parameters to match the existing pattern: create_cel_rule(expression, forward_to, domain, rank: nil, active: true, id: nil).

Copilot uses AI. Check for mistakes.

# Update a rule
# @param rule_id [String] The rule ID
# @param domain [String] The domain name
# @param config [Hash] The config object for the rule
# @param rank [Float, nil] Optional new rank
# @param active [Boolean, nil] Optional active state
# @return [Hash, false] The updated rule or false if not found
def update_rule(rule_id, domain, config: nil, rank: nil, active: nil)
data = {}
data[:config] = config if config
data[:rank] = rank if rank
data[:active] = active unless active.nil?

response = put("/domains/#{domain}/rules/#{rule_id}", data)
response.to_h['rule']
rescue NotFoundError
false
end
Comment on lines +103 to +113
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The update_rule method has an inconsistent parameter order compared to update_alias in the aliases module. The update_alias method uses update_alias(alias_name, forward_to, domain), but this uses update_rule(rule_id, domain, config: nil, ...). For consistency, consider using update_rule(rule_id, config, domain, rank: nil, active: nil) to match the pattern where the identifier comes first, then the update data, then the domain.

Copilot uses AI. Check for mistakes.

# Delete a rule
# @param rule_id [String] The rule ID
# @param domain [String] The domain name
# @return [Boolean] true if deleted or not found
def delete_rule(rule_id, domain)
response = delete("/domains/#{domain}/rules/#{rule_id}")
response.ok?
rescue NotFoundError
true
end

# Delete all rules for a domain
# @param domain [String] The domain name
# @return [Boolean] true if successful
def delete_all_rules(domain)
response = delete("/domains/#{domain}/rules-all")
response.ok?
end

# Bulk modify rules
# @param domain [String] The domain name
# @param rules [Array<Hash>] Array of rule objects
# @param behavior [String] 'add', 'update', or 'delete' (default: 'add')
# @return [Hash] Results of bulk operation
def bulk_modify_rules(domain, rules, behavior: 'add')
data = {
rules: rules,
behavior: behavior
}
Comment on lines +139 to +143
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing input validation for the behavior parameter. The documentation indicates valid values are 'add', 'update', or 'delete', but the method accepts any string value without validation. Consider adding validation to ensure only valid behavior values are accepted.

Copilot uses AI. Check for mistakes.

response = post("/domains/#{domain}/rules/bulk", data)
response.to_h
end
Comment on lines +139 to +147
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bulk_modify_rules method has an inconsistent parameter order. Looking at existing methods in the codebase, the pattern is to have the resource identifier or primary data first, then the domain. Consider reordering to bulk_modify_rules(rules, domain, behavior: 'add') for consistency.

Copilot uses AI. Check for mistakes.
end
end
Loading