File Coverage

blib/lib/WWW/Mailgun.pm
Criterion Covered Total %
statement 73 107 68.2
branch 14 38 36.8
condition 17 30 56.6
subroutine 15 19 78.9
pod 4 6 66.6
total 123 200 61.5


line stmt bran cond sub pod time code
1             package WWW::Mailgun;
2              
3 2     2   86935 use strict;
  2         2  
  2         45  
4 2     2   7 use warnings;
  2         2  
  2         45  
5              
6 2     2   547 use JSON;
  2         10154  
  2         10  
7 2     2   1056 use MIME::Base64;
  2         939  
  2         95  
8 2     2   768 use match::smart;
  2         7307  
  2         13  
9              
10             require LWP::UserAgent;
11              
12             BEGIN {
13 2     2   4342 our $VERSION = 0.54;
14             }
15              
16             my @IGNORE_DOMAIN = qw/domains/;
17             my @GET_METHODS = qw/stats domains log mailboxes/;
18             my @POST_METHODS = qw//;
19             my @ALL_METHODS = (@GET_METHODS, @POST_METHODS);
20              
21             my $ALIAS__OPTION = {
22             attachments => 'attachment',
23             tags => 'o:tag',
24             };
25              
26             my $OPTION__MAXIMUM = {
27             "o:tag" => 3,
28             };
29              
30             sub new {
31 4     4 1 875 my ($class, $param) = @_;
32              
33 4   50     16 my $Key = $param->{key} // die "You must specify an API Key";
34 4   50     10 my $Domain = $param->{domain} // die "You need to specify a domain (IE: samples.mailgun.org)";
35 4   50     21 my $Url = $param->{url} // "https://api.mailgun.net/v2";
36 4   100     15 my $From = $param->{from} // "";
37              
38 4         23 my $self = {
39             ua => LWP::UserAgent->new,
40             url => $Url . '/',
41             domain => $Domain,
42             from => $From,
43             };
44              
45             $self->{get} = sub {
46 2     2   4 my ($self, $type, $data) = @_;
47 2         10 return my $r = $self->{ua}->get(_get_route($self,[$type, $data]));
48 4         5169 };
49              
50             $self->{del} = sub {
51 1     1   3 my ($self, $type, $data) = @_;
52             return my $r = $self->{ua}->request(
53 1         6 HTTP::Request->new( 'DELETE', _get_route( $self, [$type, $data] ) )
54             );
55 4         17 };
56              
57             $self->{post} = sub {
58 4     4   5 my ($self, $type, $data) = @_;
59 4         10 return my $r = $self->{ua}->post(_get_route($self,$type), Content_Type => 'multipart/form-data', Content => $data);
60 4         13 };
61              
62 4         40 $self->{ua}->default_header('Authorization' => 'Basic ' . encode_base64('api:' . $Key));
63              
64 4         162 return bless $self, $class;
65             }
66              
67             sub _handle_response {
68 7     7   14 my ($response) = shift;
69              
70 7         18 my $rc = $response->code;
71              
72 7 50 33     86 return 1 if 200 <= $rc && $rc <= 299; # success
73              
74 0         0 my $json = from_json($response->decoded_content);
75 0 0       0 if ($json->{message}) {
76 0         0 die $response->status_line." ".$json->{message};
77             }
78              
79 0 0       0 die "Bad Request - Often missing a required parameter" if $rc == 400;
80 0 0       0 die "Unauthorized - No valid API key provided" if $rc == 401;
81 0 0       0 die "Request Failed - Parameters were valid but request failed" if $rc == 402;
82 0 0       0 die "Not Found - The requested item doesn’t exist" if $rc == 404;
83 0 0       0 die "Server Errors - something is wrong on Mailgun’s end" if $rc >= 500;
84             }
85              
86             sub send {
87 3     3 1 4 my ($self, $msg) = @_;
88              
89             $msg->{from} = $msg->{from} || $self->{from}
90 3 50 66     16 or die "You must specify an email address to send from";
91             $msg->{to} = $msg->{to}
92 3 50       8 or die "You must specify an email address to send to";
93 3 50       8 if (ref $msg->{to} eq 'ARRAY') {
94 0         0 $msg->{to} = join(',',@{$msg->{to}});
  0         0  
95             }
96              
97 3   50     8 $msg->{subject} = $msg->{subject} // "";
98 3   100     13 $msg->{text} = $msg->{text} // "";
99              
100 3         5 my $content = _prepare_content($msg);
101              
102 3         14 my $r = $self->{post}->($self, 'messages', $content);
103              
104 3         9471 _handle_response($r);
105              
106 3         12 return from_json($r->decoded_content);
107             }
108              
109             =head2 _prepare_content($option__values) : \@content
110              
111             Given a $option__values hashref, transform it to an arrayref suitable for
112             sending as multipart/form-data. The core logic here is that array references
113             are modified from:
114              
115             option => [ value1, value2, ... ]
116              
117             to
118              
119             [ option => value1, option => value2, ... ]
120              
121             =cut
122              
123             sub _prepare_content {
124 3     3   5 my ($option__values) = @_;
125              
126 3         4 my $content = [];
127 3         5 my $option__count = {};
128              
129 3         12 while (my ($option, $values) = each %$option__values) {
130 18   66     53 $option = $ALIAS__OPTION->{$option} || $option;
131 18 100       34 $values = ref $values ? $values : [$values];
132              
133 18         26 for my $value (@$values) {
134 22         27 $option__count->{$option}++;
135 22 100 100     49 if ($OPTION__MAXIMUM->{$option} &&
136             $option__count->{$option} > $OPTION__MAXIMUM->{$option}) {
137 1         162 warn "Reached max number of $option, skipping...";
138 1         9 last;
139             }
140 21 100       40 $value = [ $value ] if $option eq 'attachment';
141 21         72 push @$content, $option => $value;
142             }
143             }
144              
145 3         8 return $content;
146             }
147              
148             sub _get_route {
149 7     7   12 my ($self, $path) = @_;
150              
151 7 100       42 if (ref $path eq 'ARRAY'){
    50          
152 3         7 my @clean = grep {defined} @$path;
  6         15  
153             unshift @clean, $self->{domain}
154 3 50       13 unless $clean[-1] |M| @IGNORE_DOMAIN;
155 3         376 $path = join('/',@clean);
156             } elsif (!($path |M| @IGNORE_DOMAIN)) {
157 4         419 $path = $self->{domain} . '/' . $path
158             }
159 7         56 return $self->{url} . $path;
160             }
161              
162             sub unsubscribes {
163 4     4 1 4390 my ($self, $method, $data) = @_;
164 4   50     12 $method = $method // 'get';
165              
166 4         18 my $r = $self->{lc($method)}->($self,'unsubscribes',$data);
167 4         1676880 _handle_response($r);
168 4         21 return from_json($r->decoded_content);
169             }
170              
171             sub complaints {
172 0     0 0   my ($self, $method, $data) = @_;
173 0   0       $method = $method // 'get';
174              
175 0           my $r = $self->{lc($method)}->($self,'complaints',$data);
176 0           _handle_response($r);
177 0           return from_json($r->decoded_content);
178             }
179              
180             sub bounces {
181 0     0 1   my ($self, $method, $data) = @_;
182 0   0       $method = $method // 'get';
183              
184 0           my $r = $self->{lc($method)}->($self,'bounces',$data);
185 0           _handle_response($r);
186 0           return from_json($r->decoded_content);
187             }
188              
189             sub logs {
190             ## Legacy support.
191 0     0 0   my $self = shift;
192 0           return $self->log();
193             }
194              
195             sub AUTOLOAD { ## Handle generic list of requests.
196 0     0     our $AUTOLOAD;
197 0           my $self = shift;
198 0           my @ObjParts = split(/\:\:/, $AUTOLOAD);
199 0           my $method = pop(@ObjParts);
200 0 0         return if $method eq 'DESTROY'; ## Ignore DESTROY.
201 0 0         unless ($method |M| @ALL_METHODS) {
202 0           die("Not a valid method, \"$method\".");
203             }
204 0           my $mode = 'get';
205 0 0         $mode = 'post' if $method |M| @POST_METHODS;
206 0           my $r = $self->{$mode}->($self, $method, @_);
207 0           _handle_response($r);
208 0           return from_json($r->decoded_content);
209             }
210              
211             =pod
212              
213              
214             =head1 NAME
215              
216             WWW::Mailgun - Perl wrapper for Mailgun (L)
217              
218             =head1 SYNOPSIS
219              
220             use WWW::Mailgun;
221              
222             my $mg = WWW::Mailgun->new({
223             key => 'key-yOuRapiKeY',
224             domain => 'YourDomain.mailgun.org',
225             from => 'elb0w ' # Optionally set here, you can set it when you send
226             });
227              
228             #sending examples below
229              
230             # Get stats http://documentation.mailgun.net/api-stats.html
231             my $obj = $mg->stats;
232              
233             # Get logs http://documentation.mailgun.net/api-logs.html
234             my $obj = $mg->logs;
235              
236              
237             =head1 DESCRIPTION
238              
239             Mailgun is a email service which provides email over a http restful API.
240             These bindings goal is to create a perl interface which allows you to
241             easily leverage it.
242              
243             =head1 USAGE
244              
245             =head2 new({key => 'mailgun key', domain => 'your mailgun domain', from => 'optional from')
246              
247             Creates your mailgun object
248              
249             from => the only optional field, it can be set in the message.
250              
251              
252              
253             =head2 send($data)
254              
255             Send takes in a hash of settings
256             Takes all specificed here L
257             'from' is optionally set here, otherwise you can set it in the constructor and it can be used for everything
258              
259             =item Send a HTML message with optional array of attachments
260              
261             $mg->send({
262             to => 'some_email@gmail.com',
263             subject => 'hello',
264             html => '

hello

world',
265             attachment => ['/Users/elb0w/GIT/Personal/Mailgun/test.pl']
266             });
267              
268             =item Send a text message
269              
270             $mg->send({
271             to => 'some_email@gmail.com',
272             subject => 'hello',
273             text => 'Hello there'
274             });
275              
276             =item Send a MIME multipart message
277              
278             $mg->send({
279             to => 'some_email@gmail.com',
280             subject => 'hello',
281             text => 'Hello there',
282             html => 'Hello there'
283             });
284              
285              
286             =head2 unsubscribes, bounces, spam
287              
288             Helper methods all take a method argument (del, post, get)
289             L
290             'post' optionally takes a hash of properties
291              
292              
293             =item Unsubscribes
294              
295             # View all unsubscribes L
296             my $all = $mg->unsubscribes;
297              
298             # Unsubscribe user from all
299             $mg->unsubscribes('post',{address => 'user@website.com', tag => '*'});
300              
301             # Delete a user from unsubscriptions
302             $mg->unsubscribes('del','user@website.com');
303              
304             # Get a user from unsubscriptions
305             $mg->unsubscribes('get','user@website.com');
306              
307              
308              
309             =item Complaints
310              
311             # View all spam complaints L
312             my $all = $mg->complaints;
313              
314             # Add a spam complaint for a address
315             $mg->complaints('post',{address => 'user@website.com'});
316              
317             # Remove a complaint
318             $mg->complaints('del','user@website.com');
319              
320             # Get a complaint for a adress
321             $mg->complaints('get','user@website.com');
322              
323             =item Bounces
324              
325             # View the list of bounces L
326             my $all = $mg->bounces;
327              
328             # Add a permanent bounce
329             $mg->bounces('post',{
330             address => 'user@website.com',
331             code => 550, #This is default
332             error => 'Error Description' #Empty by default
333             });
334              
335             # Remove a bounce
336             $mg->bounces('del','user@website.com');
337              
338             # Get a bounce for a specific address
339             $mg->bounces('get','user@website.com');
340              
341             =head1 TODO
342              
343             =item Mailboxes
344              
345             =item Campaigns
346              
347             =item Mailing Lists
348              
349             =item Routes
350              
351             =head1 Author
352              
353             George Tsafas
354              
355             =head1 Support
356              
357             elb0w on irc.freenode.net #perl
358             L
359              
360              
361             =head1 Resources
362              
363             L
364              
365