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.
Files changed (4) hide show
  1. checksums.yaml +5 -5
  2. data/bin/trikle-mail +32 -118
  3. data/lib/triklemailer.rb +109 -0
  4. metadata +62 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: d1d4893cd70f9c06b996c6fb6e836d286b49d9f1
4
- data.tar.gz: 743d5bf00407d3a037ba23d1e5de77fb5b3a824a
2
+ SHA256:
3
+ metadata.gz: 5d9a65541914cfad901296c5d39cb0fb173ecc85390886f8902f8006d39d37bc
4
+ data.tar.gz: 0e4ebb9c03c7e2f2a34ab263f6aaa69584cf25095f39e65d89c0a795be503035
5
5
  SHA512:
6
- metadata.gz: e9e0bd7203ce7a7af339f53129d0f6441c2cb202d0c118309448499eabdac39bbd7869f558d318f7e3ed11d10bb2f63a5bbc758ea8e97c5685d9299ac8c754ce
7
- data.tar.gz: f07592534deba8b7ac50d29e44fbbc2f9caffa897e4415f3c0c1390d7a3721a698a32b1e304c518ef93087a0085621252e42c84e39f0dfe5a2f9733cf33bd1c6
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, '0.0.0'
12
- program :description, 'Send your bulk email in a trikle over the mandril api'
12
+ program :version, Triklemailer::VERSION
13
+ program :description, 'Send your bulk email from the cli in a trikle'
13
14
 
14
- default_command :run
15
+ default_command :smtp
15
16
 
16
17
  command :smtp do |c|
17
- c.syntax = 'trikle-mail smtp csv_of_emails.csv [options]'
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
- title_row, *data_rows = CSV.read(args[0])
32
-
33
- data_hashes = data_rows.map do |row|
34
- title_row.map.with_index do |title, index|
35
- [title.downcase.to_sym, row[index]]
36
- end.to_h
37
- end
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
- mail.delivery_method :smtp, {
64
- address: hash.fetch(:host, options.host),
65
- port: hash.fetch(:port, options.port),
66
- user_name: hash.fetch(:username, options.username),
67
- password: hash.fetch(:password, options.password),
68
- authentication: :login,
69
- enable_starttls_auto: true
70
- }
71
-
72
- puts mail.deliver!
73
- random_wait_time = rand(0..time_range)
74
- puts "waiting #{random_wait_time} seconds"
75
- sleep(random_wait_time)
76
- end
77
- end
78
- end
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
@@ -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.3
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
- rubyforge_project:
69
- rubygems_version: 2.5.1
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: