mynewsletterbuilder 1.19
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.
- data/README +32 -0
- data/lib/mynewsletterbuilder.rb +522 -0
- metadata +56 -0
data/README
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
WWW-MyNewsletterBuilder version 0.021
|
2
|
+
===============================
|
3
|
+
|
4
|
+
This is the first version of the Ruby interface for the api.mynewsletterbuilder.com
|
5
|
+
XML-RPC API. Contact support@mynewsletterbuilder.com for an API key.
|
6
|
+
|
7
|
+
INSTALLATION
|
8
|
+
|
9
|
+
To install this gem (make sure gemforge is present in your gem repo) type the following:
|
10
|
+
|
11
|
+
sudo gem install mynewsletterbuilder
|
12
|
+
or
|
13
|
+
gem install mynewsletterbuilder
|
14
|
+
|
15
|
+
|
16
|
+
Require the gem in your application
|
17
|
+
|
18
|
+
require 'rubygems'
|
19
|
+
require 'mynewsletterbuilder'
|
20
|
+
|
21
|
+
|
22
|
+
DEPENDENCIES
|
23
|
+
|
24
|
+
This module requires these other modules and libraries:
|
25
|
+
|
26
|
+
xmlrpc/client
|
27
|
+
|
28
|
+
COPYRIGHT AND LICENCE
|
29
|
+
|
30
|
+
Copyright (C) 2010 by JBA Network
|
31
|
+
|
32
|
+
Author: Devin Eldreth <devin@jbanetwork.com>
|
@@ -0,0 +1,522 @@
|
|
1
|
+
require 'xmlrpc/client'
|
2
|
+
|
3
|
+
# $Id: mynewsletterbuilder.rb 67532 2011-09-22 15:50:48Z devin $
|
4
|
+
# Super awesome magic sauce ruby wrapper
|
5
|
+
# Follwing any lack of documentation one should reference the main MNB api calls/structure.
|
6
|
+
# This is simply a wrapper.
|
7
|
+
|
8
|
+
class MyNewsletterBuilder
|
9
|
+
attr_accessor :client, :validation, :api_key, :api_secure
|
10
|
+
|
11
|
+
API_VERSION = '1.0'
|
12
|
+
BUILD = '$Revision: 67532 $'
|
13
|
+
|
14
|
+
# Instance variables for wrappers
|
15
|
+
@@api_host = 'api.mynewsletterbuilder.com'
|
16
|
+
@@timeout = 300
|
17
|
+
|
18
|
+
def initialize( api_key, validate = false, secure = true )
|
19
|
+
if !api_key
|
20
|
+
raise "An api key is required. Contact MyNewsletterBuilder to receive a key"
|
21
|
+
end
|
22
|
+
|
23
|
+
@api_key = api_key
|
24
|
+
@validation = validate
|
25
|
+
@api_secure = {
|
26
|
+
"secure" => secure,
|
27
|
+
"port" => ( secure ) ? 443 : 80
|
28
|
+
}
|
29
|
+
|
30
|
+
# Initilize the XMLRPC client for this instance
|
31
|
+
@client = XMLRPC::Client.new3({
|
32
|
+
'host' => @@api_host,
|
33
|
+
'path' => '/' + API_VERSION + '/',
|
34
|
+
'use_ssl' => @api_secure["secure"],
|
35
|
+
'port' => @api_secure["port"],
|
36
|
+
})
|
37
|
+
|
38
|
+
if !@client
|
39
|
+
raise "Unable to connect to xmlrpc server with credentials: #{@@api_host}/#{API_VERSION}/ with ssl = #{@api_secure["secure"]}"
|
40
|
+
end
|
41
|
+
|
42
|
+
@client.http_header_extra = {"MNB_API" => "MNB_API Ruby #{API_VERSION}/$Revision: 67532 $"}
|
43
|
+
@client.timeout = @@timeout
|
44
|
+
end
|
45
|
+
|
46
|
+
# public getter for checking if instance will use_ssl xmlrpc communication
|
47
|
+
def isSecure?
|
48
|
+
return @api_secure["secure"]
|
49
|
+
end
|
50
|
+
|
51
|
+
def Campaigns ( filters = [] )
|
52
|
+
end
|
53
|
+
|
54
|
+
def CampaignDetails ( *args )
|
55
|
+
id = args.shift
|
56
|
+
|
57
|
+
execute( "CampaignDetails", id )
|
58
|
+
end
|
59
|
+
|
60
|
+
def CampaignCreate ( *args )
|
61
|
+
name = args.shift
|
62
|
+
subject = args.shift
|
63
|
+
from = args.shift
|
64
|
+
reply = args.shift
|
65
|
+
html = args.shift
|
66
|
+
text = args.shift
|
67
|
+
link_tracking = args.shift
|
68
|
+
gat = args.shift
|
69
|
+
|
70
|
+
return execute(
|
71
|
+
"CampaignCreate",
|
72
|
+
name,
|
73
|
+
subject,
|
74
|
+
from,
|
75
|
+
reply,
|
76
|
+
html,
|
77
|
+
text,
|
78
|
+
link_tracking,
|
79
|
+
gat
|
80
|
+
)
|
81
|
+
end
|
82
|
+
|
83
|
+
def CampaignUpdate ( *args )
|
84
|
+
id = args.shift
|
85
|
+
details = args.shift
|
86
|
+
|
87
|
+
signature = {
|
88
|
+
'name' => { 'value' => 'string' },
|
89
|
+
'subject' => { 'value' => 'string' },
|
90
|
+
'html' => { 'value' => 'string' },
|
91
|
+
'text' => { 'value' => 'string', 'emptyOk' => true },
|
92
|
+
'link_tracking' => { 'value' => 'boolean', 'emptyOk' => true },
|
93
|
+
'gat' => { 'value' => 'boolean', 'emptyOk' => true }
|
94
|
+
}
|
95
|
+
|
96
|
+
signature['from'] = {
|
97
|
+
'value' => {
|
98
|
+
'name' => { 'value' => 'string' },
|
99
|
+
'email' => { 'value' => 'string' }
|
100
|
+
}
|
101
|
+
}
|
102
|
+
|
103
|
+
signature['reply'] = {
|
104
|
+
'value' => {
|
105
|
+
'name' => { 'value' => 'string' },
|
106
|
+
'email' => { 'value' => 'string' }
|
107
|
+
},
|
108
|
+
'emptyOk' => true
|
109
|
+
}
|
110
|
+
|
111
|
+
return execute(
|
112
|
+
"CampaignUpdate",
|
113
|
+
id,
|
114
|
+
details
|
115
|
+
)
|
116
|
+
end
|
117
|
+
|
118
|
+
def CampaignCopy ( id, name )
|
119
|
+
if !name
|
120
|
+
name = ''
|
121
|
+
end
|
122
|
+
|
123
|
+
return execute(
|
124
|
+
"CampaignCopy",
|
125
|
+
id,
|
126
|
+
name
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
def CampaignDelete ( id )
|
131
|
+
id = intify( id )
|
132
|
+
|
133
|
+
return execute(
|
134
|
+
"CampaignDelete",
|
135
|
+
id
|
136
|
+
)
|
137
|
+
end
|
138
|
+
|
139
|
+
def CampaignStats ( id )
|
140
|
+
id = intify( id )
|
141
|
+
|
142
|
+
return execute(
|
143
|
+
"CampaignStats",
|
144
|
+
id
|
145
|
+
)
|
146
|
+
end
|
147
|
+
|
148
|
+
def CampaignRecipients ( id, page = 0, limit = 1000 )
|
149
|
+
id = intify( id )
|
150
|
+
page = intify( page )
|
151
|
+
limit = intify( limit )
|
152
|
+
|
153
|
+
return execute(
|
154
|
+
"CampaignRecipients",
|
155
|
+
id,
|
156
|
+
page,
|
157
|
+
limit
|
158
|
+
)
|
159
|
+
end
|
160
|
+
|
161
|
+
def CampaignOpens ( id, page = 0, limit = 1000 )
|
162
|
+
id = intify( id )
|
163
|
+
page = intify( page )
|
164
|
+
limit = intify( limit )
|
165
|
+
|
166
|
+
return execute(
|
167
|
+
"CampaignOpens",
|
168
|
+
id,
|
169
|
+
page,
|
170
|
+
limit
|
171
|
+
)
|
172
|
+
end
|
173
|
+
|
174
|
+
def CampaignSubscribes ( id, page = 0, limit = 1000 )
|
175
|
+
id = intify( id )
|
176
|
+
page = intify( page )
|
177
|
+
limit = intify( limit )
|
178
|
+
|
179
|
+
return execute(
|
180
|
+
"CampaignSubscribes",
|
181
|
+
id,
|
182
|
+
page,
|
183
|
+
limit
|
184
|
+
)
|
185
|
+
end
|
186
|
+
|
187
|
+
def CampaignUnsubscribes ( id, page = 0, limit = 1000 )
|
188
|
+
id = intify( id )
|
189
|
+
page = intify( page )
|
190
|
+
limit = intify( limit )
|
191
|
+
|
192
|
+
return execute(
|
193
|
+
"CampaignUnsubscribes",
|
194
|
+
id,
|
195
|
+
page,
|
196
|
+
limit
|
197
|
+
)
|
198
|
+
end
|
199
|
+
|
200
|
+
def CampaignBounces ( id, page = 0, limit = 1000 )
|
201
|
+
id = intify( id )
|
202
|
+
page = intify( page )
|
203
|
+
limit = intify( limit )
|
204
|
+
|
205
|
+
return execute(
|
206
|
+
"CampaignBounces",
|
207
|
+
id,
|
208
|
+
page,
|
209
|
+
limit
|
210
|
+
)
|
211
|
+
end
|
212
|
+
|
213
|
+
def CampaignUrls ( id )
|
214
|
+
id = intify( id )
|
215
|
+
|
216
|
+
return execute(
|
217
|
+
"CampaignUrls",
|
218
|
+
id
|
219
|
+
)
|
220
|
+
end
|
221
|
+
|
222
|
+
def CampaignClicks ( id, page = 0, limit = 1000 )
|
223
|
+
id = intify( id )
|
224
|
+
page = intify( page )
|
225
|
+
limit = intify( limit )
|
226
|
+
|
227
|
+
return execute(
|
228
|
+
"CampaignClicks",
|
229
|
+
id,
|
230
|
+
page,
|
231
|
+
limit
|
232
|
+
)
|
233
|
+
end
|
234
|
+
|
235
|
+
def CampaignClickDetails ( id, url_id = 0, page = 0, limit = 1000 )
|
236
|
+
id = intify( id )
|
237
|
+
url_id = intify( url_id )
|
238
|
+
page = intify( page )
|
239
|
+
limit = intify( limit )
|
240
|
+
|
241
|
+
return execute(
|
242
|
+
"CampaignClickDetails",
|
243
|
+
id,
|
244
|
+
url_id,
|
245
|
+
page,
|
246
|
+
limit
|
247
|
+
)
|
248
|
+
end
|
249
|
+
|
250
|
+
def Lists
|
251
|
+
return execute("Lists")
|
252
|
+
end
|
253
|
+
|
254
|
+
def ListDetails ( id )
|
255
|
+
id = intify( id )
|
256
|
+
|
257
|
+
return execute(
|
258
|
+
"ListDetails",
|
259
|
+
id
|
260
|
+
)
|
261
|
+
end
|
262
|
+
|
263
|
+
def ListCreate ( name, description = '', visible = false, default = false )
|
264
|
+
name = stringify( name )
|
265
|
+
desc = stringify( description )
|
266
|
+
visible = visible
|
267
|
+
default = default
|
268
|
+
|
269
|
+
return execute(
|
270
|
+
"ListCreate",
|
271
|
+
name,
|
272
|
+
desc,
|
273
|
+
visible,
|
274
|
+
default
|
275
|
+
)
|
276
|
+
end
|
277
|
+
|
278
|
+
def ListUpdate ( id, name, details )
|
279
|
+
id = intify( id )
|
280
|
+
name = stringify( name || '' )
|
281
|
+
details = details
|
282
|
+
|
283
|
+
signature = {
|
284
|
+
'name' => { 'value' => 'string', 'emptyOk' => true },
|
285
|
+
'description' => { 'value' => 'string', 'emptyOk' => true },
|
286
|
+
'visible' => { 'value' => 'boolean', 'emptyOk' => true },
|
287
|
+
'default' => { 'value' => 'boolean', 'emptyOk' => true }
|
288
|
+
}
|
289
|
+
|
290
|
+
return execute(
|
291
|
+
"ListUpdate",
|
292
|
+
id,
|
293
|
+
name,
|
294
|
+
details
|
295
|
+
)
|
296
|
+
end
|
297
|
+
|
298
|
+
def ListDelete ( id, delete_subs = false )
|
299
|
+
id = intify( id )
|
300
|
+
|
301
|
+
return execute(
|
302
|
+
"ListDelete",
|
303
|
+
id,
|
304
|
+
delete_subs
|
305
|
+
)
|
306
|
+
end
|
307
|
+
|
308
|
+
def Subscribers ( statuses, lists, page = 0, limit = 1000 )
|
309
|
+
statuses = statuses
|
310
|
+
lists = lists
|
311
|
+
page = intify( page )
|
312
|
+
limit = intify( limit )
|
313
|
+
|
314
|
+
return execute(
|
315
|
+
"Subscribers",
|
316
|
+
statuses,
|
317
|
+
lists,
|
318
|
+
page,
|
319
|
+
limit
|
320
|
+
)
|
321
|
+
end
|
322
|
+
|
323
|
+
def SubscriberDetails ( id_or_email )
|
324
|
+
id_or_email = stringify( id_or_email )
|
325
|
+
|
326
|
+
return execute(
|
327
|
+
"SubscriberDetails",
|
328
|
+
id_or_email
|
329
|
+
)
|
330
|
+
end
|
331
|
+
|
332
|
+
def Subscribe ( details, lists, skip_opt_in = false, update_existing = true )
|
333
|
+
details = details
|
334
|
+
lists = lists
|
335
|
+
|
336
|
+
signature = {
|
337
|
+
'email' => { 'value' => 'string' },
|
338
|
+
'first_name' => { 'value' => 'string', 'emptyOk' => true },
|
339
|
+
'middle_name' => { 'value' => 'string', 'emptyOk' => true },
|
340
|
+
'last_name' => { 'value' => 'string', 'emptyOk' => true },
|
341
|
+
'full_name' => { 'value' => 'string', 'emptyOk' => true },
|
342
|
+
'company_name' => { 'value' => 'string', 'emptyOk' => true },
|
343
|
+
'job_title' => { 'value' => 'string', 'emptyOk' => true },
|
344
|
+
'phone_work' => { 'value' => 'string', 'emptyOk' => true },
|
345
|
+
'phone_home' => { 'value' => 'string', 'emptyOk' => true },
|
346
|
+
'address_1' => { 'value' => 'string', 'emptyOk' => true },
|
347
|
+
'address_2' => { 'value' => 'string', 'emptyOk' => true },
|
348
|
+
'address_3' => { 'value' => 'string', 'emptyOk' => true },
|
349
|
+
'city' => { 'value' => 'string', 'emptyOk' => true },
|
350
|
+
'state' => { 'value' => 'string', 'emptyOk' => true },
|
351
|
+
'zip' => { 'value' => 'string', 'emptyOk' => true },
|
352
|
+
'country' => { 'value' => 'string', 'emptyOk' => true }
|
353
|
+
}
|
354
|
+
|
355
|
+
return execute(
|
356
|
+
"Subscribe",
|
357
|
+
details,
|
358
|
+
lists,
|
359
|
+
skip_opt_in,
|
360
|
+
update_existing
|
361
|
+
)
|
362
|
+
end
|
363
|
+
|
364
|
+
def SubscribeBatch ( subscribers, lists, skip_opt_in = false, update_existing = true )
|
365
|
+
signature = {
|
366
|
+
'email' => { 'value' => 'string' },
|
367
|
+
'first_name' => { 'value' => 'string', 'emptyOk' => true },
|
368
|
+
'middle_name' => { 'value' => 'string', 'emptyOk' => true },
|
369
|
+
'last_name' => { 'value' => 'string', 'emptyOk' => true },
|
370
|
+
'full_name' => { 'value' => 'string', 'emptyOk' => true },
|
371
|
+
'company_name' => { 'value' => 'string', 'emptyOk' => true },
|
372
|
+
'job_title' => { 'value' => 'string', 'emptyOk' => true },
|
373
|
+
'phone_work' => { 'value' => 'string', 'emptyOk' => true },
|
374
|
+
'phone_home' => { 'value' => 'string', 'emptyOk' => true },
|
375
|
+
'address_1' => { 'value' => 'string', 'emptyOk' => true },
|
376
|
+
'address_2' => { 'value' => 'string', 'emptyOk' => true },
|
377
|
+
'address_3' => { 'value' => 'string', 'emptyOk' => true },
|
378
|
+
'city' => { 'value' => 'string', 'emptyOk' => true },
|
379
|
+
'state' => { 'value' => 'string', 'emptyOk' => true },
|
380
|
+
'zip' => { 'value' => 'string', 'emptyOk' => true },
|
381
|
+
'country' => { 'value' => 'string', 'emptyOk' => true }
|
382
|
+
}
|
383
|
+
|
384
|
+
return execute(
|
385
|
+
"SubscribeBatch",
|
386
|
+
subscribers,
|
387
|
+
lists,
|
388
|
+
skip_opt_in,
|
389
|
+
update_existing
|
390
|
+
)
|
391
|
+
end
|
392
|
+
|
393
|
+
def SubscriberUnsubscribe ( id_or_email )
|
394
|
+
id_or_email = stringify( id_or_email )
|
395
|
+
|
396
|
+
return execute(
|
397
|
+
"SubscriberUnsubscribe",
|
398
|
+
id_or_email
|
399
|
+
)
|
400
|
+
end
|
401
|
+
|
402
|
+
def SubscriberUnsubscribeBatch ( ids_or_emails )
|
403
|
+
ids_or_emails = ids_or_emails
|
404
|
+
|
405
|
+
return execute(
|
406
|
+
"SubscribeUnsubscribeBatch",
|
407
|
+
ids_or_emails
|
408
|
+
)
|
409
|
+
end
|
410
|
+
|
411
|
+
def SubscriberDelete ( id_or_email )
|
412
|
+
id_or_email = stringify( id_or_email )
|
413
|
+
|
414
|
+
return execute(
|
415
|
+
"SubscriberDelete",
|
416
|
+
id_or_email
|
417
|
+
)
|
418
|
+
end
|
419
|
+
|
420
|
+
def SubscribeDeleteBatch ( ids_or_emails )
|
421
|
+
ids_or_emails = ids_or_emails
|
422
|
+
|
423
|
+
return execute(
|
424
|
+
"SubscribeDeleteBatch",
|
425
|
+
ids_or_emails
|
426
|
+
)
|
427
|
+
end
|
428
|
+
|
429
|
+
def AccountDetails
|
430
|
+
execute( "AccountDetails", '' )
|
431
|
+
end
|
432
|
+
|
433
|
+
def AccountKeys ( username, password, disabled = false )
|
434
|
+
|
435
|
+
end
|
436
|
+
|
437
|
+
def accountKeyCreate ( username, password )
|
438
|
+
end
|
439
|
+
|
440
|
+
def accountKeyEnable ( username, password, id_or_key )
|
441
|
+
end
|
442
|
+
|
443
|
+
def accountKeyDisable ( username, password, id_or_key )
|
444
|
+
end
|
445
|
+
|
446
|
+
def HelloWorld ( val )
|
447
|
+
execute( "HelloWorld", val )
|
448
|
+
end
|
449
|
+
|
450
|
+
private
|
451
|
+
# Execute an xmlrpc call with given method name and variable arguments
|
452
|
+
def execute ( method, *args )
|
453
|
+
if !@client
|
454
|
+
raise "Unable to validate xmlrpc client connection"
|
455
|
+
end
|
456
|
+
|
457
|
+
begin
|
458
|
+
response = @client.call(method, @api_key, *args)
|
459
|
+
rescue XMLRPC::FaultException => fault
|
460
|
+
# The PHP server was returning php NULL for an empty result set... Ruby didn't like this
|
461
|
+
# return nil on the InvalidTypeEncodeException
|
462
|
+
match = fault.faultString.scan(/XML_RPC2_InvalidTypeEncodeException/)
|
463
|
+
|
464
|
+
if match
|
465
|
+
return nil
|
466
|
+
else
|
467
|
+
retry
|
468
|
+
end
|
469
|
+
else
|
470
|
+
return response
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
# Throw an error and die if data validation has been turned on
|
475
|
+
def error ( msg )
|
476
|
+
if @validation
|
477
|
+
raise "Unable to properly validate a parameter"
|
478
|
+
end
|
479
|
+
|
480
|
+
print msg
|
481
|
+
end
|
482
|
+
|
483
|
+
# Methods following this will be in private scope
|
484
|
+
def validateHash ( *args )
|
485
|
+
hash = args.shift
|
486
|
+
signature = args.shift
|
487
|
+
emptyOk = args.shift || false
|
488
|
+
|
489
|
+
if !hash and !emptyOk
|
490
|
+
puts 'errrr'
|
491
|
+
end
|
492
|
+
|
493
|
+
signature.each_pair do |key, value|
|
494
|
+
|
495
|
+
end
|
496
|
+
|
497
|
+
return hash
|
498
|
+
end
|
499
|
+
|
500
|
+
def intify ( value )
|
501
|
+
begin
|
502
|
+
Integer( value )
|
503
|
+
rescue
|
504
|
+
error( "Invalid param passed to: " + caller(1).to_s + ". Expected Integer recieved " + value.class.to_s + ". Alpha characters will be truncated." )
|
505
|
+
value = value.to_i
|
506
|
+
retry
|
507
|
+
else
|
508
|
+
return value.to_i
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
def stringify ( value )
|
513
|
+
begin
|
514
|
+
String( value )
|
515
|
+
rescue
|
516
|
+
error( "Invalid param passed to: " + caller(1).to_s + ". Expected String recieved " + value.class.to_s + "." )
|
517
|
+
return value.to_s
|
518
|
+
else
|
519
|
+
return value.to_s
|
520
|
+
end
|
521
|
+
end
|
522
|
+
end
|
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mynewsletterbuilder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: "1.19"
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Devin Eldreth
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-10-25 00:00:00 Z
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: The MNB API is an easy way to directly manipulate your mailing needs through an xmlrpc interface.
|
17
|
+
email:
|
18
|
+
- devin@jbanetwork.com
|
19
|
+
executables: []
|
20
|
+
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files: []
|
24
|
+
|
25
|
+
files:
|
26
|
+
- lib/mynewsletterbuilder.rb
|
27
|
+
- README
|
28
|
+
homepage: http://api.mynewsletterbuilder.com
|
29
|
+
licenses: []
|
30
|
+
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options: []
|
33
|
+
|
34
|
+
require_paths:
|
35
|
+
- lib
|
36
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: "0"
|
42
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.3.6
|
48
|
+
requirements: []
|
49
|
+
|
50
|
+
rubyforge_project: mynewsletterbuilder
|
51
|
+
rubygems_version: 1.7.2
|
52
|
+
signing_key:
|
53
|
+
specification_version: 3
|
54
|
+
summary: Interface with the MyNewsletterBuilder API through Ruby!
|
55
|
+
test_files: []
|
56
|
+
|