File Coverage

blib/lib/WWW/Mailgun.pm
Criterion Covered Total %
statement 70 104 67.3
branch 14 38 36.8
condition 17 30 56.6
subroutine 14 18 77.7
pod 4 6 66.6
total 119 196 60.7


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

hello

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