trikle-mail 0.0.3 → 0.0.9

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.
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: