action_mailer_kafka 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +158 -0
- data/.gitignore +18 -0
- data/.rspec +3 -0
- data/.rubocop.yml +64 -0
- data/Appraisals +21 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +4 -0
- data/LICENSE.md +23 -0
- data/README.md +138 -0
- data/Rakefile +6 -0
- data/action_mailer_kafka.gemspec +52 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/example/.gitignore +21 -0
- data/example/Gemfile +39 -0
- data/example/README.rdoc +10 -0
- data/example/Rakefile +6 -0
- data/example/app/assets/images/.keep +0 -0
- data/example/app/assets/javascripts/application.js +16 -0
- data/example/app/assets/javascripts/users.js.coffee +3 -0
- data/example/app/assets/stylesheets/application.css +15 -0
- data/example/app/assets/stylesheets/scaffolds.css.scss +69 -0
- data/example/app/assets/stylesheets/users.css.scss +3 -0
- data/example/app/controllers/application_controller.rb +5 -0
- data/example/app/controllers/concerns/.keep +0 -0
- data/example/app/controllers/users_controller.rb +82 -0
- data/example/app/helpers/application_helper.rb +2 -0
- data/example/app/helpers/users_helper.rb +2 -0
- data/example/app/jobs/send_email_job.rb +8 -0
- data/example/app/mailers/.keep +0 -0
- data/example/app/mailers/example_mailer.rb +6 -0
- data/example/app/models/.keep +0 -0
- data/example/app/models/concerns/.keep +0 -0
- data/example/app/models/user.rb +2 -0
- data/example/app/views/example_mailer/sample_email.html.erb +398 -0
- data/example/app/views/example_mailer/sample_email.text.erb +2 -0
- data/example/app/views/layouts/application.html.erb +14 -0
- data/example/app/views/users/_form.html.erb +25 -0
- data/example/app/views/users/edit.html.erb +6 -0
- data/example/app/views/users/index.html.erb +27 -0
- data/example/app/views/users/index.json.jbuilder +4 -0
- data/example/app/views/users/new.html.erb +5 -0
- data/example/app/views/users/show.html.erb +14 -0
- data/example/app/views/users/show.json.jbuilder +1 -0
- data/example/config/application.rb +23 -0
- data/example/config/boot.rb +4 -0
- data/example/config/database.yml +11 -0
- data/example/config/environment.rb +5 -0
- data/example/config/environments/development.rb +43 -0
- data/example/config/environments/production.rb +105 -0
- data/example/config/environments/test.rb +39 -0
- data/example/config/initializers/backtrace_silencers.rb +7 -0
- data/example/config/initializers/cookies_serializer.rb +3 -0
- data/example/config/initializers/filter_parameter_logging.rb +4 -0
- data/example/config/initializers/inflections.rb +16 -0
- data/example/config/initializers/mime_types.rb +4 -0
- data/example/config/initializers/session_store.rb +3 -0
- data/example/config/initializers/wrap_parameters.rb +14 -0
- data/example/config/locales/en.yml +23 -0
- data/example/config/routes.rb +4 -0
- data/example/config/secrets.yml +22 -0
- data/example/config.ru +4 -0
- data/example/db/migrate/20141111080045_create_users.rb +10 -0
- data/example/db/migrate/20141115060216_create_delayed_jobs.rb +22 -0
- data/example/db/schema.rb +40 -0
- data/example/db/seeds.rb +7 -0
- data/example/public/404.html +67 -0
- data/example/public/422.html +67 -0
- data/example/public/500.html +66 -0
- data/example/public/favicon.ico +0 -0
- data/example/public/robots.txt +5 -0
- data/example/vendor/assets/javascripts/.keep +0 -0
- data/example/vendor/assets/stylesheets/.keep +0 -0
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/mail_2_5.gemfile +7 -0
- data/gemfiles/mail_2_6.gemfile +7 -0
- data/gemfiles/mail_2_7.gemfile +7 -0
- data/gemfiles/rails_4.gemfile +7 -0
- data/gemfiles/rails_5.gemfile +7 -0
- data/lib/action_mailer_kafka/delivery_method.rb +139 -0
- data/lib/action_mailer_kafka/error.rb +22 -0
- data/lib/action_mailer_kafka/railtie.rb +7 -0
- data/lib/action_mailer_kafka/version.rb +3 -0
- data/lib/action_mailer_kafka.rb +10 -0
- data/logo.png +0 -0
- metadata +328 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
# Be sure to restart your server when you modify this file.
|
2
|
+
|
3
|
+
# Your secret key is used for verifying the integrity of signed cookies.
|
4
|
+
# If you change this key, all old signed cookies will become invalid!
|
5
|
+
|
6
|
+
# Make sure the secret is at least 30 characters and all random,
|
7
|
+
# no regular words or you'll be exposed to dictionary attacks.
|
8
|
+
# You can use `rake secret` to generate a secure secret key.
|
9
|
+
|
10
|
+
# Make sure the secrets in this file are kept private
|
11
|
+
# if you're sharing your code publicly.
|
12
|
+
|
13
|
+
development:
|
14
|
+
secret_key_base: 593bb8c4bce047d42fd6180cad2572c047c2f930ece6a2f462c96c39cda20f7b71cc73588f91b5750e8194e2ce8a16f00bc7e44a3e4922db4e4dc6c9ad250f96
|
15
|
+
|
16
|
+
test:
|
17
|
+
secret_key_base: f6ae07449275c2603fac9202afbe52f532f4a3d5131b58ba0d10408534979773d9fbfad12d5aa806d5c5e889527fac708725084563a12e72d750732765dad34c
|
18
|
+
|
19
|
+
# Do not keep production secrets in the repository,
|
20
|
+
# instead read values from the environment.
|
21
|
+
production:
|
22
|
+
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
|
data/example/config.ru
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
class CreateDelayedJobs < ActiveRecord::Migration[5.0]
|
2
|
+
def self.up
|
3
|
+
create_table :delayed_jobs, :force => true do |table|
|
4
|
+
table.integer :priority, :default => 0, :null => false # Allows some jobs to jump to the front of the queue
|
5
|
+
table.integer :attempts, :default => 0, :null => false # Provides for retries, but still fail eventually.
|
6
|
+
table.text :handler, :null => false # YAML-encoded string of the object that will do work
|
7
|
+
table.text :last_error # reason for last failure (See Note below)
|
8
|
+
table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
|
9
|
+
table.datetime :locked_at # Set when a client is working on this object
|
10
|
+
table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
|
11
|
+
table.string :locked_by # Who is working on this object (if locked)
|
12
|
+
table.string :queue # The name of the queue this job is in
|
13
|
+
table.timestamps
|
14
|
+
end
|
15
|
+
|
16
|
+
add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority'
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.down
|
20
|
+
drop_table :delayed_jobs
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# This file is auto-generated from the current state of the database. Instead
|
2
|
+
# of editing this file, please use the migrations feature of Active Record to
|
3
|
+
# incrementally modify your database, and then regenerate this schema definition.
|
4
|
+
#
|
5
|
+
# Note that this schema.rb definition is the authoritative source for your
|
6
|
+
# database schema. If you need to create the application database on another
|
7
|
+
# system, you should be using db:schema:load, not running all the migrations
|
8
|
+
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
9
|
+
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
10
|
+
#
|
11
|
+
# It's strongly recommended that you check this file into your version control system.
|
12
|
+
|
13
|
+
ActiveRecord::Schema.define(version: 2014_11_15_060216) do
|
14
|
+
|
15
|
+
# These are extensions that must be enabled in order to support this database
|
16
|
+
enable_extension "plpgsql"
|
17
|
+
|
18
|
+
create_table "delayed_jobs", id: :serial, force: :cascade do |t|
|
19
|
+
t.integer "priority", default: 0, null: false
|
20
|
+
t.integer "attempts", default: 0, null: false
|
21
|
+
t.text "handler", null: false
|
22
|
+
t.text "last_error"
|
23
|
+
t.datetime "run_at"
|
24
|
+
t.datetime "locked_at"
|
25
|
+
t.datetime "failed_at"
|
26
|
+
t.string "locked_by"
|
27
|
+
t.string "queue"
|
28
|
+
t.datetime "created_at", null: false
|
29
|
+
t.datetime "updated_at", null: false
|
30
|
+
t.index ["priority", "run_at"], name: "delayed_jobs_priority"
|
31
|
+
end
|
32
|
+
|
33
|
+
create_table "users", id: :serial, force: :cascade do |t|
|
34
|
+
t.string "name"
|
35
|
+
t.string "email"
|
36
|
+
t.datetime "created_at", null: false
|
37
|
+
t.datetime "updated_at", null: false
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
data/example/db/seeds.rb
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# This file should contain all the record creation needed to seed the database with its default values.
|
2
|
+
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
|
3
|
+
#
|
4
|
+
# Examples:
|
5
|
+
#
|
6
|
+
# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
|
7
|
+
# Mayor.create(name: 'Emanuel', city: cities.first)
|
@@ -0,0 +1,67 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>The page you were looking for doesn't exist (404)</title>
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
6
|
+
<style>
|
7
|
+
body {
|
8
|
+
background-color: #EFEFEF;
|
9
|
+
color: #2E2F30;
|
10
|
+
text-align: center;
|
11
|
+
font-family: arial, sans-serif;
|
12
|
+
margin: 0;
|
13
|
+
}
|
14
|
+
|
15
|
+
div.dialog {
|
16
|
+
width: 95%;
|
17
|
+
max-width: 33em;
|
18
|
+
margin: 4em auto 0;
|
19
|
+
}
|
20
|
+
|
21
|
+
div.dialog > div {
|
22
|
+
border: 1px solid #CCC;
|
23
|
+
border-right-color: #999;
|
24
|
+
border-left-color: #999;
|
25
|
+
border-bottom-color: #BBB;
|
26
|
+
border-top: #B00100 solid 4px;
|
27
|
+
border-top-left-radius: 9px;
|
28
|
+
border-top-right-radius: 9px;
|
29
|
+
background-color: white;
|
30
|
+
padding: 7px 12% 0;
|
31
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
32
|
+
}
|
33
|
+
|
34
|
+
h1 {
|
35
|
+
font-size: 100%;
|
36
|
+
color: #730E15;
|
37
|
+
line-height: 1.5em;
|
38
|
+
}
|
39
|
+
|
40
|
+
div.dialog > p {
|
41
|
+
margin: 0 0 1em;
|
42
|
+
padding: 1em;
|
43
|
+
background-color: #F7F7F7;
|
44
|
+
border: 1px solid #CCC;
|
45
|
+
border-right-color: #999;
|
46
|
+
border-left-color: #999;
|
47
|
+
border-bottom-color: #999;
|
48
|
+
border-bottom-left-radius: 4px;
|
49
|
+
border-bottom-right-radius: 4px;
|
50
|
+
border-top-color: #DADADA;
|
51
|
+
color: #666;
|
52
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
53
|
+
}
|
54
|
+
</style>
|
55
|
+
</head>
|
56
|
+
|
57
|
+
<body>
|
58
|
+
<!-- This file lives in public/404.html -->
|
59
|
+
<div class="dialog">
|
60
|
+
<div>
|
61
|
+
<h1>The page you were looking for doesn't exist.</h1>
|
62
|
+
<p>You may have mistyped the address or the page may have moved.</p>
|
63
|
+
</div>
|
64
|
+
<p>If you are the application owner check the logs for more information.</p>
|
65
|
+
</div>
|
66
|
+
</body>
|
67
|
+
</html>
|
@@ -0,0 +1,67 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>The change you wanted was rejected (422)</title>
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
6
|
+
<style>
|
7
|
+
body {
|
8
|
+
background-color: #EFEFEF;
|
9
|
+
color: #2E2F30;
|
10
|
+
text-align: center;
|
11
|
+
font-family: arial, sans-serif;
|
12
|
+
margin: 0;
|
13
|
+
}
|
14
|
+
|
15
|
+
div.dialog {
|
16
|
+
width: 95%;
|
17
|
+
max-width: 33em;
|
18
|
+
margin: 4em auto 0;
|
19
|
+
}
|
20
|
+
|
21
|
+
div.dialog > div {
|
22
|
+
border: 1px solid #CCC;
|
23
|
+
border-right-color: #999;
|
24
|
+
border-left-color: #999;
|
25
|
+
border-bottom-color: #BBB;
|
26
|
+
border-top: #B00100 solid 4px;
|
27
|
+
border-top-left-radius: 9px;
|
28
|
+
border-top-right-radius: 9px;
|
29
|
+
background-color: white;
|
30
|
+
padding: 7px 12% 0;
|
31
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
32
|
+
}
|
33
|
+
|
34
|
+
h1 {
|
35
|
+
font-size: 100%;
|
36
|
+
color: #730E15;
|
37
|
+
line-height: 1.5em;
|
38
|
+
}
|
39
|
+
|
40
|
+
div.dialog > p {
|
41
|
+
margin: 0 0 1em;
|
42
|
+
padding: 1em;
|
43
|
+
background-color: #F7F7F7;
|
44
|
+
border: 1px solid #CCC;
|
45
|
+
border-right-color: #999;
|
46
|
+
border-left-color: #999;
|
47
|
+
border-bottom-color: #999;
|
48
|
+
border-bottom-left-radius: 4px;
|
49
|
+
border-bottom-right-radius: 4px;
|
50
|
+
border-top-color: #DADADA;
|
51
|
+
color: #666;
|
52
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
53
|
+
}
|
54
|
+
</style>
|
55
|
+
</head>
|
56
|
+
|
57
|
+
<body>
|
58
|
+
<!-- This file lives in public/422.html -->
|
59
|
+
<div class="dialog">
|
60
|
+
<div>
|
61
|
+
<h1>The change you wanted was rejected.</h1>
|
62
|
+
<p>Maybe you tried to change something you didn't have access to.</p>
|
63
|
+
</div>
|
64
|
+
<p>If you are the application owner check the logs for more information.</p>
|
65
|
+
</div>
|
66
|
+
</body>
|
67
|
+
</html>
|
@@ -0,0 +1,66 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>We're sorry, but something went wrong (500)</title>
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
6
|
+
<style>
|
7
|
+
body {
|
8
|
+
background-color: #EFEFEF;
|
9
|
+
color: #2E2F30;
|
10
|
+
text-align: center;
|
11
|
+
font-family: arial, sans-serif;
|
12
|
+
margin: 0;
|
13
|
+
}
|
14
|
+
|
15
|
+
div.dialog {
|
16
|
+
width: 95%;
|
17
|
+
max-width: 33em;
|
18
|
+
margin: 4em auto 0;
|
19
|
+
}
|
20
|
+
|
21
|
+
div.dialog > div {
|
22
|
+
border: 1px solid #CCC;
|
23
|
+
border-right-color: #999;
|
24
|
+
border-left-color: #999;
|
25
|
+
border-bottom-color: #BBB;
|
26
|
+
border-top: #B00100 solid 4px;
|
27
|
+
border-top-left-radius: 9px;
|
28
|
+
border-top-right-radius: 9px;
|
29
|
+
background-color: white;
|
30
|
+
padding: 7px 12% 0;
|
31
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
32
|
+
}
|
33
|
+
|
34
|
+
h1 {
|
35
|
+
font-size: 100%;
|
36
|
+
color: #730E15;
|
37
|
+
line-height: 1.5em;
|
38
|
+
}
|
39
|
+
|
40
|
+
div.dialog > p {
|
41
|
+
margin: 0 0 1em;
|
42
|
+
padding: 1em;
|
43
|
+
background-color: #F7F7F7;
|
44
|
+
border: 1px solid #CCC;
|
45
|
+
border-right-color: #999;
|
46
|
+
border-left-color: #999;
|
47
|
+
border-bottom-color: #999;
|
48
|
+
border-bottom-left-radius: 4px;
|
49
|
+
border-bottom-right-radius: 4px;
|
50
|
+
border-top-color: #DADADA;
|
51
|
+
color: #666;
|
52
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
53
|
+
}
|
54
|
+
</style>
|
55
|
+
</head>
|
56
|
+
|
57
|
+
<body>
|
58
|
+
<!-- This file lives in public/500.html -->
|
59
|
+
<div class="dialog">
|
60
|
+
<div>
|
61
|
+
<h1>We're sorry, but something went wrong.</h1>
|
62
|
+
</div>
|
63
|
+
<p>If you are the application owner check the logs for more information.</p>
|
64
|
+
</div>
|
65
|
+
</body>
|
66
|
+
</html>
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,139 @@
|
|
1
|
+
module ActionMailerKafka
|
2
|
+
class DeliveryMethod
|
3
|
+
SUPPORTED_MULTIPART_MIME_TYPES = ['multipart/alternative', 'multipart/mixed', 'multipart/related'].freeze
|
4
|
+
attr_accessor :settings
|
5
|
+
attr_reader :mailer_topic_name, :kafka_client, :kafka_publish_proc
|
6
|
+
|
7
|
+
# settings params allow you to pass in
|
8
|
+
# 1. Your Kafka publish proc
|
9
|
+
# With this option, you should config as below:
|
10
|
+
# config.action_mailer.action_mailer_kafka_settings = {
|
11
|
+
# kafka_mail_topic: 'YourKafkaTopic',
|
12
|
+
# kafka_publish_proc: proc do |message_data, default_message_topic|
|
13
|
+
# YourKafkaClientInstance.publish(message_data,
|
14
|
+
# default_message_topic)
|
15
|
+
# end
|
16
|
+
# }
|
17
|
+
#
|
18
|
+
# and the data would go through your publish process
|
19
|
+
#
|
20
|
+
# 2. Your kafka client info
|
21
|
+
# With this option, the library will generate a kafka instance for you:
|
22
|
+
# config.action_mailer.action_mailer_kafka_settings = {
|
23
|
+
# kafka_mail_topic: 'YourKafkaTopic',
|
24
|
+
# kafka_client_info: {
|
25
|
+
# seed_brokers: ['localhost:9090'],
|
26
|
+
# logger: logger,
|
27
|
+
# ssl_ca_cert: '/path/to/cert'
|
28
|
+
# # For more option on what to pass here, see https://github.com/zendesk/ruby-kafka/blob/master/lib/kafka/client.rb#L20
|
29
|
+
# }
|
30
|
+
# }
|
31
|
+
#
|
32
|
+
# Other settings params:
|
33
|
+
# - raise_on_delivery_error
|
34
|
+
# - logger
|
35
|
+
# - fallback
|
36
|
+
# + fallback_delivery_method
|
37
|
+
# + fallback_delivery_method_settings
|
38
|
+
# }
|
39
|
+
|
40
|
+
def initialize(**params)
|
41
|
+
@settings = params
|
42
|
+
@service_name = params[:service_name] || ''
|
43
|
+
@mailer_topic_name = @settings.fetch(:kafka_mail_topic)
|
44
|
+
if @settings[:fallback]
|
45
|
+
@fallback_delivery_method = Mail::Configuration.instance.lookup_delivery_method(
|
46
|
+
@settings[:fallback].fetch(:fallback_delivery_method)
|
47
|
+
).new(
|
48
|
+
@settings[:fallback].fetch(:fallback_delivery_method_settings)
|
49
|
+
)
|
50
|
+
end
|
51
|
+
if @settings[:kafka_publish_proc]
|
52
|
+
@kafka_publish_proc = @settings[:kafka_publish_proc]
|
53
|
+
else
|
54
|
+
@kafka_client = ::Kafka.new(@settings.fetch(:kafka_client_info))
|
55
|
+
@kafka_publish_proc = proc { |data, topic|
|
56
|
+
kafka_client.deliver_message(data, topic: topic)
|
57
|
+
}
|
58
|
+
end
|
59
|
+
rescue KeyError => e
|
60
|
+
raise RequiredParamsError.new(params, e.message)
|
61
|
+
end
|
62
|
+
|
63
|
+
def logger
|
64
|
+
@settings[:logger] || Logger.new(STDOUT)
|
65
|
+
end
|
66
|
+
|
67
|
+
def deliver!(mail)
|
68
|
+
mail_data = construct_mail_data mail
|
69
|
+
kafka_publish_proc.call(mail_data, mailer_topic_name)
|
70
|
+
rescue Kafka::Error => e
|
71
|
+
error_msg = "Fail to send email into Kafka due to: #{e.message}. Delivered using fallback method"
|
72
|
+
logger.error(error_msg)
|
73
|
+
@fallback_delivery_method.deliver!(mail) if @settings[:fallback]
|
74
|
+
raise KafkaOperationError, error_msg if @settings[:raise_on_delivery_error]
|
75
|
+
rescue StandardError => e
|
76
|
+
error_msg = "Fail to send email due to: #{e.message}"
|
77
|
+
logger.error(error_msg)
|
78
|
+
raise ParsingOperationError, error_msg if @settings[:raise_on_delivery_error]
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def construct_mail_data(mail)
|
84
|
+
general_data = {
|
85
|
+
subject: mail.subject,
|
86
|
+
from: mail.from,
|
87
|
+
to: mail.to,
|
88
|
+
cc: mail.cc,
|
89
|
+
bcc: mail.bcc,
|
90
|
+
mime_type: mail.mime_type,
|
91
|
+
author: @service_name
|
92
|
+
}
|
93
|
+
general_data.merge! construct_mail_body(mail)
|
94
|
+
general_data.merge! construct_custom_mail_header(mail)
|
95
|
+
general_data[:attachments] = construct_attachments mail
|
96
|
+
general_data.to_json
|
97
|
+
end
|
98
|
+
|
99
|
+
def construct_custom_mail_header(mail)
|
100
|
+
result = { custom_headers: {} }
|
101
|
+
mail.header_fields.each do |h|
|
102
|
+
header_name = h.name
|
103
|
+
# header_value = h.unparsed_value
|
104
|
+
# Ideally header values should not be parsed and sent directly to the mail service
|
105
|
+
# However, Field #unparsed_value is not available on Mail Gem version 2.5 and before
|
106
|
+
# here header.value got its value parsed, so a string should be expected
|
107
|
+
# even if you create a custom header with a hash
|
108
|
+
header_value = h.value
|
109
|
+
if h.field.is_a?(::Mail::OptionalField) && header_name.start_with?('X-')
|
110
|
+
result[:custom_headers][header_name] = header_value
|
111
|
+
end
|
112
|
+
end
|
113
|
+
result
|
114
|
+
end
|
115
|
+
|
116
|
+
def construct_attachments(mail)
|
117
|
+
mail.attachments.map { |part| convert_attachment part }
|
118
|
+
end
|
119
|
+
|
120
|
+
def convert_attachment(part)
|
121
|
+
{
|
122
|
+
content: Base64.strict_encode64(part.body.decoded),
|
123
|
+
type: part.mime_type,
|
124
|
+
filename: part.filename
|
125
|
+
}
|
126
|
+
end
|
127
|
+
|
128
|
+
def construct_mail_body(mail)
|
129
|
+
if SUPPORTED_MULTIPART_MIME_TYPES.include?(mail.mime_type)
|
130
|
+
{
|
131
|
+
text_part: mail.text_part&.decoded,
|
132
|
+
html_part: mail.html_part&.decoded
|
133
|
+
}
|
134
|
+
else
|
135
|
+
{ body: mail.body&.decoded }
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|