brevo-extras 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: a72266c6461dcf2931b8e1db860f7c2b292fb67a12e447974f8f3f2fe38232b7
4
+ data.tar.gz: 8a0179c5cb72befd993590538b828dd2149e8c24c02fb0249e30e9868f8df2ea
5
+ SHA512:
6
+ metadata.gz: 5dcd2ea1fbd345dd700c4746390cb608203bc4a3dd12bbe7f8b02eef8c03e37e460364816325e4c466da61a58a0abad8991828854ff95b40b4532f0610e20daf
7
+ data.tar.gz: c9a2f577a21cd4a3ac4126d8b194ed1b6aa2fa634cd24d89a96441ac02d3230e18fae8893632ed34c3eb7fb7823a13a1b2957f021242ed353aee7b6b2cde3f37
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright Bruno Perles
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,113 @@
1
+ [![CI](https://github.com/atnos/brevo-extras/actions/workflows/ci.yml/badge.svg)](https://github.com/atnos/brevo-extras/actions/workflows/ci.yml)
2
+ [![CodeQL](https://github.com/atnos/brevo-extras/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/atnos/brevo-extras/actions/workflows/github-code-scanning/codeql)
3
+ [![Dependabot Updates](https://github.com/atnos/brevo-extras/actions/workflows/dependabot/dependabot-updates/badge.svg)](https://github.com/atnos/brevo-extras/actions/workflows/dependabot/dependabot-updates)
4
+
5
+ # Brevo::Extras
6
+
7
+ A Rails engine that provides a clean abstraction layer for sending transactional emails via the [Brevo](https://www.brevo.com/) API, with built-in safety features to prevent accidental email delivery in development and test environments.
8
+
9
+ ## Features
10
+
11
+ - Abstract base class for creating email sender classes
12
+ - Asynchronous delivery via Active Job
13
+ - Automatic retry on API errors with polynomial backoff
14
+ - **Sandbox mode** - prevents actual email delivery (enabled by default)
15
+ - **Safe mode** - filters recipients by allowed domains (enabled by default in local environments)
16
+ - Template-based emails with parameters
17
+ - Reply-to address support
18
+
19
+ ## Installation
20
+
21
+ Add this line to your application's Gemfile:
22
+
23
+ ```ruby
24
+ gem "brevo-extras", github: "atnos/brevo-extras"
25
+ ```
26
+
27
+ And then execute:
28
+
29
+ ```bash
30
+ $ bundle
31
+ ```
32
+
33
+ ## Configuration
34
+
35
+ The engine is configured through environment variables:
36
+
37
+ | Variable | Default | Description |
38
+ |---|---|---|
39
+ | `BREVO_SANDBOX_MODE` | `"1"` | When enabled, adds `X-Sib-Sandbox: drop` header so Brevo accepts the request but does not send the email |
40
+ | `BREVO_SAFE_MODE` | `"1"` | When enabled, filters recipients to only allowed domains. Always active in local Rails environments (`development`, `test`) regardless of this setting |
41
+ | `BREVO_SAFE_MODE_ALLOWED_DOMAINS` | `""` | Comma-separated list of allowed email domains (e.g. `"example.com,mycompany.com"`) |
42
+
43
+ You also need to configure the Brevo API key as required by the [brevo gem](https://github.com/getbrevo/brevo-ruby).
44
+
45
+ ## Usage
46
+
47
+ ### Creating an email sender
48
+
49
+ Subclass `Brevo::Extras::Base` and implement the `#call` method:
50
+
51
+ ```ruby
52
+ class WelcomeEmail < Brevo::Extras::Base
53
+ def call
54
+ send_email(
55
+ template_id: 1,
56
+ to: [{ email: params[:email], name: params[:name] }]
57
+ )
58
+ end
59
+ end
60
+ ```
61
+
62
+ ### Sending an email
63
+
64
+ ```ruby
65
+ WelcomeEmail.call(email: "user@example.com", name: "John")
66
+ ```
67
+
68
+ The email is enqueued as an Active Job and delivered asynchronously.
69
+
70
+ ### Reply-to support
71
+
72
+ ```ruby
73
+ send_email(
74
+ template_id: 1,
75
+ to: [{ email: params[:email], name: params[:name] }],
76
+ reply_to: { email: "support@example.com" }
77
+ )
78
+ ```
79
+
80
+ ### Template parameters
81
+
82
+ Parameters passed to `.call` are forwarded to the Brevo template as `params`:
83
+
84
+ ```ruby
85
+ # These params will be available in your Brevo template
86
+ OrderConfirmation.call(
87
+ email: "user@example.com",
88
+ order_id: "12345",
89
+ total: "$99.00"
90
+ )
91
+ ```
92
+
93
+ ### Retry behavior
94
+
95
+ The `DeliveryJob` automatically retries on `Brevo::ApiError` with polynomial backoff, up to 5 attempts.
96
+
97
+ ## Development
98
+
99
+ ### Running tests
100
+
101
+ ```bash
102
+ bin/rails test
103
+ ```
104
+
105
+ ### Linting
106
+
107
+ ```bash
108
+ bin/rubocop
109
+ ```
110
+
111
+ ## License
112
+
113
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ require "bundler/gem_tasks"
@@ -0,0 +1,6 @@
1
+ module Brevo
2
+ module Extras
3
+ module ApplicationHelper
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Brevo
2
+ module Extras
3
+ class ApplicationJob < ActiveJob::Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brevo
4
+ module Extras
5
+ class DeliveryJob < ApplicationJob
6
+ retry_on Brevo::ApiError, wait: :polynomially_longer, attempts: 5
7
+
8
+ def perform(data)
9
+ api_instance = Brevo::TransactionalEmailsApi.new
10
+ api_instance.send_transac_email(
11
+ Brevo::SendSmtpEmail.new(data)
12
+ )
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ module Brevo
2
+ module Extras
3
+ class ApplicationMailer < ActionMailer::Base
4
+ default from: "from@example.com"
5
+ layout "mailer"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ module Brevo
2
+ module Extras
3
+ class ApplicationRecord < ActiveRecord::Base
4
+ self.abstract_class = true
5
+ end
6
+ end
7
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ Brevo::Extras::Engine.routes.draw do
2
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brevo
4
+ module Extras
5
+ class Base
6
+ class << self
7
+ def call(params)
8
+ new(params).call
9
+ end
10
+ end
11
+
12
+ def initialize(params)
13
+ @params = params
14
+ end
15
+
16
+ def call
17
+ raise NotImplementedError, "Subclasses must implement #call"
18
+ end
19
+
20
+ protected
21
+
22
+ def send_email(template_id:, to:, reply_to: nil)
23
+ data = build_email_data(template_id:, to:, reply_to:)
24
+ data[:to] = safe_mode_recipients(data[:to])
25
+ data[:headers] = { "X-Sib-Sandbox" => "drop" } if sandbox_mode?
26
+ deliver_later(data)
27
+ end
28
+
29
+ private
30
+
31
+ def build_email_data(template_id:, to:, reply_to:)
32
+ {
33
+ templateId: template_id,
34
+ to: to,
35
+ replyTo: reply_to,
36
+ params: @params
37
+ }.tap do |data|
38
+ data.delete(:replyTo) if reply_to.nil?
39
+ end
40
+ end
41
+
42
+ def safe_mode_recipients(recipients)
43
+ return recipients unless safe_mode?
44
+
45
+ recipients = recipients.is_a?(Array) ? recipients : [ recipients ]
46
+ recipients.select do |recipient|
47
+ email = recipient[:email].to_s.gsub(/\s/, "").downcase
48
+ safe_mode_domains.any? { |domain| email.end_with?("@#{domain}") }
49
+ end
50
+ end
51
+
52
+ def sandbox_mode?
53
+ ActiveModel::Type::Boolean.new.cast(
54
+ ENV.fetch("BREVO_SANDBOX_MODE", "1")
55
+ )
56
+ end
57
+
58
+ def safe_mode_domains
59
+ ENV.fetch("BREVO_SAFE_MODE_ALLOWED_DOMAINS", "").split(",").map(&:strip)
60
+ end
61
+
62
+ def safe_mode?
63
+ ActiveModel::Type::Boolean.new.cast(
64
+ ENV.fetch("BREVO_SAFE_MODE", "1")
65
+ ) || Rails.env.local?
66
+ end
67
+
68
+ def deliver_later(data)
69
+ DeliveryJob.perform_later(data)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,7 @@
1
+ module Brevo
2
+ module Extras
3
+ class Engine < ::Rails::Engine
4
+ isolate_namespace Brevo::Extras
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module Brevo
2
+ module Extras
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ require "brevo/extras/version"
2
+ require "brevo/extras/engine"
3
+ require "brevo/extras/base"
4
+ require "brevo"
5
+
6
+ module Brevo
7
+ module Extras
8
+ # Your code goes here...
9
+ end
10
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :brevo_extras do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: brevo-extras
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bruno Perles
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: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 8.1.2
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 8.1.2
26
+ - !ruby/object:Gem::Dependency
27
+ name: brevo
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '4.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '4.0'
40
+ description: A Rails engine that provides a clean abstraction layer for sending transactional
41
+ emails via the Brevo API, with built-in sandbox and safe modes, async delivery via
42
+ Active Job, and automatic retry with polynomial backoff.
43
+ email:
44
+ - contact@atnos.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - MIT-LICENSE
50
+ - README.md
51
+ - Rakefile
52
+ - app/helpers/brevo/extras/application_helper.rb
53
+ - app/jobs/brevo/extras/application_job.rb
54
+ - app/jobs/brevo/extras/delivery_job.rb
55
+ - app/mailers/brevo/extras/application_mailer.rb
56
+ - app/models/brevo/extras/application_record.rb
57
+ - config/routes.rb
58
+ - lib/brevo/extras.rb
59
+ - lib/brevo/extras/base.rb
60
+ - lib/brevo/extras/engine.rb
61
+ - lib/brevo/extras/version.rb
62
+ - lib/tasks/brevo/extras_tasks.rake
63
+ homepage: https://github.com/atnos/brevo-extras
64
+ licenses:
65
+ - MIT
66
+ metadata:
67
+ allowed_push_host: https://rubygems.org
68
+ homepage_uri: https://github.com/atnos/brevo-extras
69
+ source_code_uri: https://github.com/atnos/brevo-extras
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubygems_version: 4.0.3
85
+ specification_version: 4
86
+ summary: A Rails engine for sending transactional emails via the Brevo API.
87
+ test_files: []