trikle-mail 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/bin/trikle-mail +18 -120
- data/lib/triklemailer.rb +98 -0
- metadata +45 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1bc1f6ac3d5593c2b07c79554fd73ac007913e2b8e257d044611328eca81d1c5
|
4
|
+
data.tar.gz: 779eb9f435e07e043f91d5eff45d2092a16076049157dbbbfaa1ee785b99cf5b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b9635c1c57938ea53c143458d48587234855f6b3910b1877c3f99b9bbedef48403748289338c38c2344b27e1915eec6806a8a3dd269225be85661d62fcc8eaa
|
7
|
+
data.tar.gz: baee1a420a5bcc5cd1ca280b3caee0aae9a0af0f98ff7d8423f49a7f90ab3e0c32cb639773decd0c3021a28edbf66ebbb21ef5d2c196741608099be655c2568e
|
data/bin/trikle-mail
CHANGED
@@ -3,18 +3,19 @@ require 'commander'
|
|
3
3
|
require 'mandrill'
|
4
4
|
require 'csv'
|
5
5
|
require 'mail'
|
6
|
+
require 'triklemailer'
|
6
7
|
|
7
8
|
class TrikleMail
|
8
9
|
extend Commander::Methods
|
9
10
|
|
10
11
|
program :name, 'Trikle Mail'
|
11
|
-
program :version,
|
12
|
-
program :description, 'Send your bulk email in a trikle
|
12
|
+
program :version, Triklemailer::VERSION
|
13
|
+
program :description, 'Send your bulk email from the cli in a trikle'
|
13
14
|
|
14
|
-
default_command :
|
15
|
+
default_command :smtp
|
15
16
|
|
16
17
|
command :smtp do |c|
|
17
|
-
c.syntax = 'trikle-mail
|
18
|
+
c.syntax = 'trikle-mail csv_of_emails.csv [options]'
|
18
19
|
c.description = 'Send a message to each email in the csv using the template'
|
19
20
|
c.option '--host STRING', String, 'The host of the mail server e.g. <mail.gmail.com>'
|
20
21
|
c.option '--port INTEGER', Integer, 'Port number of the mail server e.g. 587'
|
@@ -28,122 +29,19 @@ class TrikleMail
|
|
28
29
|
c.option '--minutes INTEGER', Integer, 'The number of minutes over which to send the emails'
|
29
30
|
|
30
31
|
c.action do |args, options|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
data_hashes.each do |hash|
|
45
|
-
template = File.read(hash.fetch(:template, options.template)).
|
46
|
-
gsub(/%(?!{)/, '%%') % hash
|
47
|
-
mail = Mail.new do
|
48
|
-
from hash.fetch(:from, options.from)
|
49
|
-
to hash.fetch(:to, hash.fetch(:email) { raise 'no email address found in to column!' })
|
50
|
-
subject hash.fetch(:subject, options.subject)
|
51
|
-
|
52
|
-
if options.html
|
53
|
-
html_part do
|
54
|
-
content_type 'text/html; charset=UTF-8'
|
55
|
-
body template
|
56
|
-
end
|
57
|
-
else
|
58
|
-
text_part do
|
59
|
-
body template
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
mail.delivery_method :smtp, {
|
65
|
-
address: hash.fetch(:host, options.host),
|
66
|
-
port: hash.fetch(:port, options.port),
|
67
|
-
user_name: hash.fetch(:username, options.username),
|
68
|
-
password: hash.fetch(:password, options.password),
|
69
|
-
authentication: :login,
|
70
|
-
enable_starttls_auto: true
|
71
|
-
}
|
72
|
-
|
73
|
-
puts mail.deliver!
|
74
|
-
random_wait_time = rand(0..time_range)
|
75
|
-
puts "waiting #{random_wait_time} seconds"
|
76
|
-
sleep(random_wait_time)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
command :run do |c|
|
82
|
-
c.syntax = 'trikle-mail csv_of_emails.csv [options]'
|
83
|
-
c.description = 'Send a message to each email address in the csv'
|
84
|
-
c.option '--apikey STRING', String, 'The Mandrill API key'
|
85
|
-
c.option '--from_email STRING', String, 'The email address of the sender'
|
86
|
-
c.option '--from_name STRING', String, 'The name of the sender'
|
87
|
-
c.option '--subject STRING', String, 'The subject line of the message'
|
88
|
-
c.option '--template STRING', String, 'The template slug of the message template'
|
89
|
-
c.option '--subaccount STRING', String, 'The mandril subaccount for this batch of email'
|
90
|
-
c.option '--hours INTEGER', Integer, 'The number of hours over which to send the emails'
|
91
|
-
c.option '--minutes INTEGER', Integer, 'The number of minutes over which to send the emails'
|
92
|
-
|
93
|
-
c.action do |args, options|
|
94
|
-
api = Mandrill::API.new(options.apikey)
|
95
|
-
|
96
|
-
title_row, *data_rows = CSV.read(args[0])
|
97
|
-
|
98
|
-
data_hashes = data_rows.map do |row|
|
99
|
-
title_row.map.with_index do |title, index|
|
100
|
-
[title.downcase, row[index]]
|
101
|
-
end.to_h
|
102
|
-
end
|
103
|
-
|
104
|
-
options.default({hours: 0, minutes: 0})
|
105
|
-
time_range = ((options.hours * 60 * 60) + (options.minutes * 60)) /
|
106
|
-
data_hashes.length.to_f
|
107
|
-
|
108
|
-
data_hashes.each do |hash|
|
109
|
-
response = api.messages.send_template(
|
110
|
-
hash['template'] || options.template,
|
111
|
-
[],
|
112
|
-
{
|
113
|
-
subaccount: hash['subaccount'] || options.subaccount,
|
114
|
-
subject: hash['subject'] || options.subject,
|
115
|
-
from_email: hash['from_email'] || options.from_email,
|
116
|
-
from_name: hash['from_name'] || options.from_name,
|
117
|
-
to: [{
|
118
|
-
email: hash['email'],
|
119
|
-
name: hash['name'],
|
120
|
-
type: 'to'
|
121
|
-
}],
|
122
|
-
merge_vars: [{
|
123
|
-
rcpt: hash['email'],
|
124
|
-
vars: hash.map { |key, value| { name: key, content: value } }
|
125
|
-
}]
|
126
|
-
}
|
127
|
-
)
|
128
|
-
TrikleMail.log(response)
|
129
|
-
random_wait_time = rand(0..time_range)
|
130
|
-
puts "waiting #{random_wait_time} seconds"
|
131
|
-
sleep(random_wait_time)
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def self.log(json)
|
137
|
-
Array(json).each do |entry|
|
138
|
-
# write to log file
|
139
|
-
File.open('./log.json', 'a') { |f| f.write(JSON.dump(entry) + "\n") }
|
140
|
-
# write nicely to cli
|
141
|
-
puts [
|
142
|
-
entry['status'],
|
143
|
-
'email to',
|
144
|
-
entry['email'],
|
145
|
-
entry['reject_reason'] ? "becuase #{entry['reject_reason']}" : ''
|
146
|
-
].join(' ')
|
32
|
+
recipients = CSV.new(args[0], headers: true).map(&:to_h)
|
33
|
+
sent_file_name = ['sent', options.template.split('.').first].join('_')
|
34
|
+
sent = CSV.new(sent_file_name, headers: true).map { |r| r[:email] }
|
35
|
+
|
36
|
+
mail_options = Triklemailer::Options.new(
|
37
|
+
template: File.read(options.template),
|
38
|
+
template_name: options.template,
|
39
|
+
is_html: options.template.end_with?('html'),
|
40
|
+
from: options.from,
|
41
|
+
subject: options.subject
|
42
|
+
)
|
43
|
+
|
44
|
+
Triklemailer.send_mail(mail_options, recipients, sent)
|
147
45
|
end
|
148
46
|
end
|
149
47
|
end
|
data/lib/triklemailer.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'mail'
|
2
|
+
require 'csv'
|
3
|
+
|
4
|
+
module Triklemailer
|
5
|
+
VERSION = '0.0.6'
|
6
|
+
Options = Struct.new(:template, :template_name, :is_html, :from, :subject, keyword_init: true)
|
7
|
+
|
8
|
+
# It should take:
|
9
|
+
# * A list of emails and data
|
10
|
+
# * SMTP account details
|
11
|
+
# * A template
|
12
|
+
# * SMTP Server details
|
13
|
+
#
|
14
|
+
# and it should send an email to each email address from the csv using
|
15
|
+
# the template provided, filled in with the meta data provided for the
|
16
|
+
# email address. When each email is sent it should record that in a
|
17
|
+
# separate csv called sent_<template-name>.csv. Failures to send a
|
18
|
+
# messages should not be recorded. When sending emails it should check
|
19
|
+
# the sent_<template-name>.csv and if it exists, only send emails to
|
20
|
+
# email address not included in the sent_<template-name>.csv file.
|
21
|
+
#
|
22
|
+
# Read provided csv
|
23
|
+
# Check for sent_ csv
|
24
|
+
# For each email, meta_data in provided csv
|
25
|
+
# If email in sent_ csv
|
26
|
+
# Next email
|
27
|
+
#
|
28
|
+
# Read template file
|
29
|
+
# Apply meta_data to template file
|
30
|
+
# Send email with data from cli or meta data with template
|
31
|
+
# If there was an error sending
|
32
|
+
# Next email
|
33
|
+
# Record email sent to template in sent_ csv
|
34
|
+
#
|
35
|
+
def self.send_mail(options, recipients, sent)
|
36
|
+
cleaned_template = options.template.gsub(/%(?!{)/, '%%')
|
37
|
+
|
38
|
+
recipients.
|
39
|
+
reject { |recipient| sent.include?(recipient[:email]) }.
|
40
|
+
each do |recipient|
|
41
|
+
mail = Mail.new do
|
42
|
+
from recipient.fetch(:from, options.from)
|
43
|
+
to recipient.fetch(:to, recipient.fetch(:email) {
|
44
|
+
raise "Some rows are missing either a 'to' or 'email' column."
|
45
|
+
})
|
46
|
+
subject recipient.fetch(:subject, options.subject)
|
47
|
+
|
48
|
+
if options.is_html
|
49
|
+
html_part do
|
50
|
+
content_type 'text/html; charset=UTF-8'
|
51
|
+
body cleaned_template % recipient
|
52
|
+
end
|
53
|
+
else
|
54
|
+
text_part do
|
55
|
+
body cleaned_template % recipient
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
if block_given?
|
61
|
+
yield(mail, recipient)
|
62
|
+
else
|
63
|
+
mail.delivery_method :smtp, {
|
64
|
+
address: recipient.fetch(:host, options.host),
|
65
|
+
port: recipient.fetch(:port, options.port),
|
66
|
+
user_name: recipient.fetch(:username, options.username),
|
67
|
+
password: recipient.fetch(:password, options.password),
|
68
|
+
authentication: :login,
|
69
|
+
enable_starttls_auto: true
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
begin
|
74
|
+
mail.deliver!
|
75
|
+
# might fail in here though...
|
76
|
+
log_sent(
|
77
|
+
recipient.fetch(:to, recipient[:email]),
|
78
|
+
options.template_name)
|
79
|
+
rescue => e
|
80
|
+
puts "Failed to send email to #{recipient[:email]}."
|
81
|
+
next
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def log_sent(email, template)
|
87
|
+
log_file_name = ['sent', template.split('.').first].join('_')
|
88
|
+
|
89
|
+
CSV.open(
|
90
|
+
"./#{log_file_name}.csv",
|
91
|
+
'a',
|
92
|
+
write_headers: true,
|
93
|
+
headers: ['email', 'sent_at']
|
94
|
+
) do |csv|
|
95
|
+
csv << [email, Time.now]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trikle-mail
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jasper Lyons
|
@@ -38,6 +38,48 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: mail
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: mail
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2'
|
41
83
|
description: Send bulk email in a trickle over mandrill
|
42
84
|
email: jasper.lyons@gmail.com
|
43
85
|
executables:
|
@@ -46,6 +88,7 @@ extensions: []
|
|
46
88
|
extra_rdoc_files: []
|
47
89
|
files:
|
48
90
|
- bin/trikle-mail
|
91
|
+
- lib/triklemailer.rb
|
49
92
|
homepage: ''
|
50
93
|
licenses:
|
51
94
|
- MIT
|
@@ -65,10 +108,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
108
|
- !ruby/object:Gem::Version
|
66
109
|
version: '0'
|
67
110
|
requirements: []
|
68
|
-
|
69
|
-
rubygems_version: 2.5.1
|
111
|
+
rubygems_version: 3.0.3
|
70
112
|
signing_key:
|
71
113
|
specification_version: 4
|
72
114
|
summary: Send a trickle of mail
|
73
115
|
test_files: []
|
74
|
-
has_rdoc:
|