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