postal 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -1
- data/Gemfile +3 -0
- data/Gemfile.lock +35 -0
- data/LICENSE +1 -1
- data/README.md +108 -0
- data/lib/postal.rb +2 -6
- data/lib/postal/content.rb +14 -15
- data/lib/postal/driver.rb +121 -0
- data/lib/postal/mailing.rb +66 -41
- data/lib/postal/member.rb +33 -15
- data/lib/postal/version.rb +3 -0
- data/postal.gemspec +27 -16
- data/test/content_test.rb +2 -2
- data/test/list_test.rb +1 -1
- data/test/mailing_test.rb +1 -1
- data/test/member_test.rb +13 -5
- data/test/test_helper.rb +1 -2
- metadata +49 -52
- data/README.rdoc +0 -108
- data/lib/postal/lmapi/lmapi.rb +0 -1321
- data/lib/postal/lmapi/lmapi_driver.rb +0 -687
- data/lib/postal/lmapi/lmapi_mapping_registry.rb +0 -1237
data/.gitignore
CHANGED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
postal (0.3.0)
|
5
|
+
savon (= 0.9.7)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
akami (1.0.0)
|
11
|
+
gyoku (>= 0.4.0)
|
12
|
+
builder (3.0.0)
|
13
|
+
gyoku (0.4.4)
|
14
|
+
builder (>= 2.1.2)
|
15
|
+
httpi (0.9.5)
|
16
|
+
rack
|
17
|
+
nokogiri (1.5.0)
|
18
|
+
nori (1.0.2)
|
19
|
+
rack (1.4.1)
|
20
|
+
savon (0.9.7)
|
21
|
+
akami (~> 1.0)
|
22
|
+
builder (>= 2.1.2)
|
23
|
+
gyoku (>= 0.4.0)
|
24
|
+
httpi (~> 0.9)
|
25
|
+
nokogiri (>= 1.4.0)
|
26
|
+
nori (~> 1.0)
|
27
|
+
wasabi (~> 2.0)
|
28
|
+
wasabi (2.0.0)
|
29
|
+
nokogiri (>= 1.4.0)
|
30
|
+
|
31
|
+
PLATFORMS
|
32
|
+
ruby
|
33
|
+
|
34
|
+
DEPENDENCIES
|
35
|
+
postal!
|
data/LICENSE
CHANGED
data/README.md
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
# Introduction
|
2
|
+
|
3
|
+
Postal is a gem for working with the Lyris API. "But the Lyris API is just a SOAP service, why
|
4
|
+
don't I use something like Soap4r?" Well you could, but it wouldn't be very pretty. When the
|
5
|
+
interface is created all properties passed to a method are required and always in a certain order.
|
6
|
+
|
7
|
+
Postal does use the Soap4r interface behind the scenes but gives you a simple ActiveRecord-like
|
8
|
+
interface for search and adding records.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Get the gem:
|
13
|
+
|
14
|
+
gem sources -a http://gems.github.com
|
15
|
+
sudo gem install cannikin-postal
|
16
|
+
|
17
|
+
Then to use just add the require to your script:
|
18
|
+
|
19
|
+
require 'rubygems'
|
20
|
+
require 'postal'
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
Postal feels a lot like using ActiveRecord. You create/save new Members, find existing ones, etc.
|
25
|
+
All of the examples below assume you've already set up Postal with your username, password and WSDL
|
26
|
+
endpoint, and optionally the name of the list that all operations should use:
|
27
|
+
|
28
|
+
Postal.options[:wsdl] = 'http://mymailserver.com:82/?wsdl'
|
29
|
+
Postal.options[:username] = 'username'
|
30
|
+
Postal.options[:password] = 'password'
|
31
|
+
Postal.options[:list_name] = 'my-test-list'
|
32
|
+
|
33
|
+
If you don't set your list name in the options you can pass it in with each method call.
|
34
|
+
|
35
|
+
See the /test directory for examples of just about everything you can do with Postal.
|
36
|
+
|
37
|
+
### Lists
|
38
|
+
|
39
|
+
# find a list based on its name
|
40
|
+
Postal::List.find('my-list-name')
|
41
|
+
|
42
|
+
### Members
|
43
|
+
|
44
|
+
# add a member to a list (the list saved to Postal.options)
|
45
|
+
new_member = Postal::Member.new(:email => "john.doe@anonymous.com", :name => "John Doe")
|
46
|
+
id = new_member.save
|
47
|
+
# id => 1234567 (the new member_id in Lyris)
|
48
|
+
|
49
|
+
# add a member to a specific list and save in one call (both lines are equivalent)
|
50
|
+
Postal::Member.new(:email => "john.doe@anonymous.com", :name => "John Doe", :list_name => "new-list").save
|
51
|
+
Postal::Member.create(:email => "john.doe@anonymous.com", :name => "John Doe", :list_name => "new-list")
|
52
|
+
|
53
|
+
Postal also has a `save!` method that throws an error if the person can't be saved, rather than just
|
54
|
+
returning false.
|
55
|
+
|
56
|
+
# update a member's demographics
|
57
|
+
new_member = Postal::Member.new(:email => "john.doe@anonymous.com", :name => "John Doe")
|
58
|
+
new_member.save
|
59
|
+
new_member.update_attributes(:field_0 => 'Male')
|
60
|
+
|
61
|
+
# find a member's id based on an email address
|
62
|
+
Postal::Member.find('john.doe@anonymous.com')
|
63
|
+
|
64
|
+
# find a member based on member_id
|
65
|
+
Postal::Member.find(1234567)
|
66
|
+
|
67
|
+
# find a member based on Lyris "filters" (see Lyris API docs for syntax)
|
68
|
+
Postal::Member.find_by_filter('EmailAddress like john.doe%')
|
69
|
+
|
70
|
+
# delete member(s) based on filters
|
71
|
+
Postal::Member.destroy("EmailAddress like john.doe-delete%")
|
72
|
+
|
73
|
+
### Mailings
|
74
|
+
|
75
|
+
# directly send an email to an array of email addresses
|
76
|
+
mail = Postal::Mailing.new( :to => ['john.doe@anonymous.com','jane.doe@anonymous.com'],
|
77
|
+
:html_message => "<p>Test from Postal at #{Time.now.to_s}</p>",
|
78
|
+
:text_message => 'Test test from Postal at #{Time.now.to_s}',
|
79
|
+
:from => 'Postmaster <postmaster@company.com>',
|
80
|
+
:subject => 'Test from Postal')
|
81
|
+
mail.valid?
|
82
|
+
mail.send
|
83
|
+
|
84
|
+
Mailings require a couple things to be valid, mainly someone to send to, a subject, and
|
85
|
+
the name of a list to send to. `mail.valid?` will return false if any of these do not exist.
|
86
|
+
|
87
|
+
## To Do
|
88
|
+
|
89
|
+
* Implement wrappers for remaining Lyris methods
|
90
|
+
|
91
|
+
## Thanks
|
92
|
+
|
93
|
+
Special thanks to this article: http://markthomas.org/2007/09/12/getting-started-with-soap4r/
|
94
|
+
Mark details how to use Soap4r (for which there is very little documentation in the world) and
|
95
|
+
his example includes talking to Lyris! Postal probably couldn't have happened without his article.
|
96
|
+
|
97
|
+
## Note on Patches/Pull Requests
|
98
|
+
|
99
|
+
* Fork the project.
|
100
|
+
* Make your feature addition or bug fix.
|
101
|
+
* Add tests for it. This is important so I don't break it in a
|
102
|
+
future version unintentionally.
|
103
|
+
* Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
104
|
+
* Send me a pull request. Bonus points for topic branches.
|
105
|
+
|
106
|
+
## Copyright
|
107
|
+
|
108
|
+
Copyright (c) 2009 The Active Network. See LICENSE for details.
|
data/lib/postal.rb
CHANGED
@@ -4,9 +4,7 @@ $:.unshift File.dirname(__FILE__) # for use/testing when no gem is installed
|
|
4
4
|
require 'logger'
|
5
5
|
|
6
6
|
# internal
|
7
|
-
require 'postal/
|
8
|
-
require 'postal/lmapi/lmapi_driver.rb'
|
9
|
-
require 'postal/lmapi/lmapi_mapping_registry.rb'
|
7
|
+
require 'postal/driver'
|
10
8
|
require 'postal/base'
|
11
9
|
require 'postal/content'
|
12
10
|
require 'postal/list'
|
@@ -37,9 +35,7 @@ module Postal
|
|
37
35
|
# Make a driver instance available at Postal.driver
|
38
36
|
def driver
|
39
37
|
unless @driver
|
40
|
-
@driver = Postal::
|
41
|
-
@driver.options['protocol.http.basic_auth'] << [@options[:wsdl], @options[:username], @options[:password]]
|
42
|
-
@driver.options['protocol.http.proxy'] = @options[:proxy]
|
38
|
+
@driver = Postal::Driver.new @options[:wsdl], @options[:proxy], @options[:username], @options[:password]
|
43
39
|
end
|
44
40
|
return @driver
|
45
41
|
end
|
data/lib/postal/content.rb
CHANGED
@@ -7,22 +7,21 @@ module Postal
|
|
7
7
|
unless args.find { |arg| arg.match(/ListName/) }
|
8
8
|
args << "ListName=#{Postal.options[:list_name]}"
|
9
9
|
end
|
10
|
-
if soap_contents = Postal.driver.selectContent(args)
|
10
|
+
if soap_contents = Postal.driver.selectContent(args)[:item]
|
11
11
|
contents = soap_contents.collect do |content|
|
12
|
-
|
13
|
-
|
14
|
-
:
|
15
|
-
:
|
16
|
-
:
|
17
|
-
:
|
18
|
-
:
|
19
|
-
:
|
20
|
-
:
|
21
|
-
:
|
22
|
-
:
|
23
|
-
:
|
24
|
-
:
|
25
|
-
:title => content.title)
|
12
|
+
Content.new(:content_id => content[:content_id],
|
13
|
+
:date_created => content[:date_created],
|
14
|
+
:description => content[:description],
|
15
|
+
:doc_parts => content[:doc_parts],
|
16
|
+
:doc_type => content[:doc_yype],
|
17
|
+
:header_from => content[:header_from],
|
18
|
+
:header_to => content[:header_to],
|
19
|
+
:is_read_only => content[:is_read_only],
|
20
|
+
:is_template => content[:is_template],
|
21
|
+
:list_name => content[:list_name],
|
22
|
+
:native_title => content[:native_title],
|
23
|
+
:site_name => content[:site_name],
|
24
|
+
:title => content[:title])
|
26
25
|
end
|
27
26
|
if contents.size == 1
|
28
27
|
return contents.first
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# Main class used by client programs
|
2
|
+
|
3
|
+
require 'savon'
|
4
|
+
|
5
|
+
module Postal
|
6
|
+
class Driver
|
7
|
+
|
8
|
+
attr_accessor :client
|
9
|
+
|
10
|
+
DefaultEndpointUrl = "http://www.mymailserver.com/"
|
11
|
+
|
12
|
+
def initialize(wsdl_url = nil, proxy = nil, username = nil, password = nil)
|
13
|
+
wsdl_url ||= DefaultEndpointUrl
|
14
|
+
@client = Savon::Client.new do |wsdl, http|
|
15
|
+
wsdl.document = wsdl_url
|
16
|
+
if proxy
|
17
|
+
http.proxy = proxy
|
18
|
+
end
|
19
|
+
if username && password
|
20
|
+
http.auth.basic username, password
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def soap_actions
|
27
|
+
@client.wsdl.soap_actions
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
def selectMembers(args)
|
32
|
+
response = @client.request :wsdl, :select_members do |soap|
|
33
|
+
soap.body = { "FilterCriteriaArray" => {'item' => args} }
|
34
|
+
end
|
35
|
+
response.to_hash[:select_members_response][:return]
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def createSingleMember(email, name, list_name)
|
40
|
+
response = @client.request :wsdl, :create_single_member do |soap|
|
41
|
+
soap.body = { "EmailAddress" => email,
|
42
|
+
"FullName" => name || "",
|
43
|
+
"ListName" => list_name}
|
44
|
+
end
|
45
|
+
response.to_hash[:create_single_member_response][:return].to_i
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def deleteMembers(args)
|
50
|
+
response = @client.request :wsdl, :delete_members do |soap|
|
51
|
+
soap.body = { "FilterCriteriaArray" => {'item' => args} }
|
52
|
+
end
|
53
|
+
response.to_hash[:delete_members_response][:return].to_i
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
def unsubscribe(list_name, id, email)
|
58
|
+
response = @client.request :wsdl, :unsubscribe do |soap|
|
59
|
+
soap.body = { 'SimpleMemberStructArrayIn' => {
|
60
|
+
'item' => {
|
61
|
+
'ListName' => list_name,
|
62
|
+
'MemberID' => id,
|
63
|
+
'EmailAddress' => email }}}
|
64
|
+
end
|
65
|
+
response.to_hash[:unsubscribe_response][:return]
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
def updateMemberDemographics(list_name, id, email, demographics)
|
70
|
+
response = @client.request :wsdl, :update_member_demographics do |soap|
|
71
|
+
soap.body = { "SimpleMemberStructIn" => {
|
72
|
+
'ListName' => list_name,
|
73
|
+
'MemberID' => id,
|
74
|
+
'EmailAddress' => email},
|
75
|
+
"DemographicsArray" => {'item' => demographics} }
|
76
|
+
end
|
77
|
+
response.to_hash[:update_member_demographics_response][:return]
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
def selectContent(args)
|
82
|
+
response = @client.request :wsdl, :select_content do |soap|
|
83
|
+
soap.body = { "FilterCriteriaArray" => {'item' => args} }
|
84
|
+
end
|
85
|
+
response.to_hash[:select_content_response][:return]
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
def selectLists(list_name, site_name)
|
90
|
+
response = @client.request :wsdl, :select_lists do |soap|
|
91
|
+
soap.body = { "ListName" => list_name,
|
92
|
+
"SiteName" => site_name}
|
93
|
+
end
|
94
|
+
if response.to_hash[:select_lists_response]
|
95
|
+
return response.to_hash[:select_lists_response][:return]
|
96
|
+
else
|
97
|
+
return nil
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
def importContent(content_id)
|
103
|
+
response = @client.request :wsdl, :import_content do |soap|
|
104
|
+
soap.body = { "ContentID" => content_id }
|
105
|
+
end
|
106
|
+
response.to_hash[:import_content_response][:return]
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
def sendMailingDirect(email_addresses=[], member_ids=[], mail)
|
111
|
+
response = @client.request :wsdl, :send_mailing_direct do |soap|
|
112
|
+
soap.body = { "EmailAddressArray" => {:item => email_addresses},
|
113
|
+
"MemberIDArray" => {:item => member_ids},
|
114
|
+
"MailingStructIn" => mail }
|
115
|
+
end
|
116
|
+
response.to_hash[:send_mailing_direct_response][:return]
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
data/lib/postal/mailing.rb
CHANGED
@@ -4,7 +4,7 @@ module Postal
|
|
4
4
|
class << self
|
5
5
|
|
6
6
|
def import(content_id)
|
7
|
-
mail = Postal.driver.importContent(content_id)
|
7
|
+
mail = Mailing.new(Postal.driver.importContent(content_id))
|
8
8
|
return mail
|
9
9
|
end
|
10
10
|
|
@@ -106,17 +106,17 @@ module Postal
|
|
106
106
|
@mailing = args[:mailing]
|
107
107
|
else
|
108
108
|
@subject = args[:mailing].subject
|
109
|
-
@is_html_section_encoded = args[:mailing].
|
110
|
-
@html_section_encoding = args[:mailing].
|
111
|
-
@html_message = args[:mailing].
|
112
|
-
@char_set_id = args[:mailing].
|
113
|
-
@is_text_section_encoded = args[:mailing].
|
114
|
-
@text_section_encoding = args[:mailing].
|
109
|
+
@is_html_section_encoded = args[:mailing].is_html_section_encoded
|
110
|
+
@html_section_encoding = args[:mailing].html_section_encoding
|
111
|
+
@html_message = args[:mailing].html_message
|
112
|
+
@char_set_id = args[:mailing].char_set_id
|
113
|
+
@is_text_section_encoded = args[:mailing].is_text_section_encoded
|
114
|
+
@text_section_encoding = args[:mailing].text_section_encoding
|
115
115
|
@title = args[:mailing].title
|
116
|
-
@text_message = args[:mailing].
|
116
|
+
@text_message = args[:mailing].text_message
|
117
117
|
@attachments = args[:mailing].attachments
|
118
118
|
@from = args[:mailing].from
|
119
|
-
@additional_headers = args[:mailing].
|
119
|
+
@additional_headers = args[:mailing].additional_headers
|
120
120
|
@mailing = args[:mailing]
|
121
121
|
end
|
122
122
|
end
|
@@ -127,48 +127,73 @@ module Postal
|
|
127
127
|
if valid?
|
128
128
|
|
129
129
|
# are we sending to a list of email addresses or member ids
|
130
|
-
case @to.
|
130
|
+
case [@to].flatten.first
|
131
131
|
when ::String
|
132
|
-
emails = @to.
|
132
|
+
emails = [@to].flatten
|
133
133
|
member_ids = []
|
134
134
|
when ::Fixnum
|
135
135
|
emails = []
|
136
|
-
member_ids = @to.
|
136
|
+
member_ids = [@to].flatten
|
137
137
|
end
|
138
138
|
|
139
139
|
if @mailing.nil?
|
140
|
-
mail =
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
140
|
+
mail = { 'AdditionalHeaders' => @additional_headers,
|
141
|
+
'Attachments' => @attachments,
|
142
|
+
'BypassModeration' => @bypass_moderation,
|
143
|
+
'Campaign' => @campaign,
|
144
|
+
'CharSetID' => @char_set_id,
|
145
|
+
'DetectHtml' => @detect_html,
|
146
|
+
'DontAttemptAfterDate' => @dont_attempt_after_date,
|
147
|
+
'EnableRecovery' => @enable_recovery,
|
148
|
+
'From' => @from,
|
149
|
+
'HtmlMessage' => @html_message,
|
150
|
+
'HtmlSectionEncoding' => @html_section_encoding,
|
151
|
+
'IsHtmlSectionEncoded' => @is_html_section_encoded,
|
152
|
+
'IsTextSectionEncoded' => @is_text_section_encoded,
|
153
|
+
'ListName' => @list_name,
|
154
|
+
'RecencyNumberOfMailings' => @recency_number_of_mailings,
|
155
|
+
'RecencyWhich' => @recency_which,
|
156
|
+
'ReplyTo' => @reply_to,
|
157
|
+
'ResendAfterDays' => @resend_after_days,
|
158
|
+
'SampleSize' => @sample_size,
|
159
|
+
'ScheduledMailingDate' => @scheduled_mailing_date,
|
160
|
+
'Subject' => @subject,
|
161
|
+
'TextMessage' => @text_message,
|
162
|
+
'TextSectionEncoding' => @text_section_encoding,
|
163
|
+
'Title' => @title,
|
164
|
+
# :to => @to,
|
165
|
+
'TrackOpens' => @track_opens,
|
166
|
+
'RewriteDateWhenSent' => @rewrite_date_when_sent }
|
167
167
|
|
168
168
|
return Postal.driver.sendMailingDirect(emails,member_ids,mail)
|
169
169
|
else
|
170
|
-
mail =
|
171
|
-
|
170
|
+
mail = { 'AdditionalHeaders' => @mailing.additional_headers,
|
171
|
+
'Attachments' => @mailing.attachments,
|
172
|
+
'BypassModeration' => @mailing.bypass_moderation,
|
173
|
+
'Campaign' => @mailing.campaign,
|
174
|
+
'CharSetID' => @mailing.char_set_id,
|
175
|
+
'DetectHtml' => @mailing.detect_html,
|
176
|
+
'DontAttemptAfterDate' => @mailing.dont_attempt_after_date,
|
177
|
+
'EnableRecovery' => @mailing.enable_recovery,
|
178
|
+
'From' => @mailing.from,
|
179
|
+
'HtmlMessage' => @mailing.html_message,
|
180
|
+
'HtmlSectionEncoding' => @mailing.html_section_encoding,
|
181
|
+
'IsHtmlSectionEncoded' => @mailing.is_html_section_encoded,
|
182
|
+
'IsTextSectionEncoded' => @mailing.is_text_section_encoded,
|
183
|
+
'RecencyNumberOfMailings' => @mailing.recency_number_of_mailings,
|
184
|
+
'RecencyWhich' => @mailing.recency_which,
|
185
|
+
'ReplyTo' => @mailing.reply_to,
|
186
|
+
'ResendAfterDays' => @mailing.resend_after_days,
|
187
|
+
'SampleSize' => @mailing.sample_size,
|
188
|
+
'ScheduledMailingDate' => @mailing.scheduled_mailing_date,
|
189
|
+
'Subject' => @mailing.subject,
|
190
|
+
'TextMessage' => @mailing.text_message,
|
191
|
+
'TextSectionEncoding' => @mailing.text_section_encoding,
|
192
|
+
'Title' => @mailing.title,
|
193
|
+
# :to => @to,
|
194
|
+
'TrackOpens' => @mailing.track_opens,
|
195
|
+
'RewriteDateWhenSent' => @mailing.rewrite_date_when_sent,
|
196
|
+
'ListName' => @mailing.list_name || Postal.options[:list_name] }
|
172
197
|
return Postal.driver.sendMailingDirect(emails,member_ids,mail)
|
173
198
|
end
|
174
199
|
|