The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strict;
use warnings;
package Mail::SendGrid;
{
  $Mail::SendGrid::VERSION = '0.07';
}
# ABSTRACT: interface to SendGrid.com mail gateway APIs

use 5.008;
use Mouse 0.94;
use HTTP::Tiny 0.013;
use JSON 2.53;
use URI::Escape 3.30;
use Carp 1.20;

use Mail::SendGrid::Bounce;

has 'api_user'  => (is => 'ro', isa => 'Str', required => 1);
has 'api_key'   => (is => 'ro', isa => 'Str', required => 1);
has 'ua'        => (is => 'ro', default => sub { HTTP::Tiny->new(); });

my %valid_params =
(

    'bounces.get' =>
    {
        days       => '\d+',
        start_date => '\d\d\d\d-\d\d-\d\d',
        end_date   => '\d\d\d\d-\d\d-\d\d',
        limit      => '\d+',
        offset     => '\d+',
        type       => 'hard|soft',
        email      => '\S+@\S+',
    },

    'bounces.delete' =>
    {
        start_date => '\d\d\d\d-\d\d-\d\d',
        end_date   => '\d\d\d\d-\d\d-\d\d',
        type       => 'hard|soft',
        email      => '\S+@\S+',
    },

);

sub bounces
{
    my $self     = shift;
    my %opts     = @_;
    my $response;
    my $url;
    my $bounce_list;
    my (@bounces, $bounce);

    $response = $self->_make_request('bounces.get', \%opts, { date => 1 });

    if ($response->{success}) {
        $bounce_list = decode_json($response->{content});
        foreach my $bounce_details (@{ $bounce_list }) {
            $bounce = Mail::SendGrid::Bounce->new($bounce_details);
            push(@bounces, $bounce) if defined($bounce);
        }
    }

    return @bounces;
}

sub delete_bounces
{
    my $self = shift;
    my %opts     = @_;
    my $base_uri = 'https://sendgrid.com/api/bounces.delete.json';
    my $response;
    my $json;
    my $url;

    $response = $self->_make_request('bounces.delete', \%opts, {});

    if ($response->{success}) {
        $json = decode_json($response->{content});
        if ($json->{message} eq 'success') {
            return 1;
        } elsif (exists($json->{message})) {
            carp "bounces.delete failed - error message: $json->{message}\n";
        } else {
            carp "unexpected response from SendGrid: $response->{content}\n";
        }
    }

    return 0;
}

#
# _make_request
#
# Helper function to build the URL and then make the request to SendGrid
# $action is the part of the endpoint that identifies the action
#   eg for getting bounces, the base URL is https://sendgrid.com/api/bounces.get.json
#   and $action will be 'bounces.get'
# $optref is a hash reference that contains any options passed by the caller of the
#   public function
# $defaults is a hashref containing any defaults that we want to mix in,
#   regardless of what the user passed to the public function
#
sub _make_request
{
    my $self     = shift;
    my $action   = shift;
    my $optref   = shift;
    my $defaults = shift;
    my %params   = (
                    api_user => $self->api_user,
                    api_key  => $self->api_key,
                    %$defaults,
                   );
    my $url      = 'https://sendgrid.com/api/'.$action.'.json';
    my $response;

    foreach my $opt (keys %$optref) {
        if (not exists($valid_params{$action}->{$opt})) {
            carp "Mail::SendGrid unknown parameter '$opt' for $action";
            return 0;
        }
        if ((not defined($optref->{$opt})) || ($optref->{$opt} !~ /^($valid_params{$action}->{$opt})$/)) {
            carp "Mail::SendGrid invalid value '$optref->{$opt}' for parameter '$opt' to $action";
            return 0;
        }
        $params{$opt} = $optref->{$opt};
    }

    $url .= '?'.join('&', map { $_.'='.uri_escape($params{$_}) } keys %params);

    $response = $self->ua->get($url);
    if (not $response->{success}) {
        carp __PACKAGE__, " : $action HTTP request failed\n",
             " status code = $response->{status}\n",
             " reason      = $response->{reason}\n";
    }

    return $response;
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Mail::SendGrid - interface to SendGrid.com mail gateway APIs

=head1 VERSION

version 0.07

=head1 SYNOPSIS

 use Mail::SendGrid;
 
 $sendgrid = Mail::SendGrid->new('api_user' => '...', 'api_key' => '...');
 print "Email to the following addresses bounced:\n";
 foreach my $bounce ($sendgrid->bounces) {
     print "\t", $bounce->email, "\n";
 }
 
 $sendgrid->delete_bounces(email => 'neilb@cpan.org');

=head1 DESCRIPTION

This module provides easy access to the APIs provided by sendgrid.com, a service for sending emails.
At the moment the module just provides the C<bounces()> and C<delete_bounces()> methods.
Over time I'll add more of the SendGrid API.

=head1 METHODS

=head2 new

Takes two parameters, api_user and api_key, which were specified when you registered your account
with SendGrid. These are required.

=head2 bounces ( %params )

This requests bounces from SendGrid,
and returns a list of Mail::SendGrid::Bounce objects.
By default it will pull back all bounces, but you can use the following
parameters to constrain which bounces are returned:

=over 4

=item days => N

Number of days in the past for which to return bounces.
Today counts as the first day.

=item start_date => 'YYYY-MM-DD'

The start of the date range for which to retrieve bounces.
The date must be in ISO 8601 date format.

=item end_date => 'YYYY-MM-DD'

The end of the date range for which to retrieve bounces.
The date must be in ISO 8601 date format.

=item limit => N

The maximum number of bounces that should be returned.

=item offset => N

An offset into the list of bounces.

=item type => 'hard' | 'soft'

Limit the returns to either hard or soft bounces. A soft bounce is one which would have
a 4xx SMTP status code, a persistent transient failure. A hard bounce is one which would
have a 5xx SMTP status code, or a permanent failure.

=item email => 'email-address'

Only return bounces for the specified email address.

=back

For example, to get a list of all soft bounces over the last week, you would use:

  @bounces = $sendgrid->bounces(type => 'soft', days => 7);

=head2 delete_bounces( %options )

This is used to delete one or more bounces, or even all bounces;
the following options constrain which bounces are deleted.
For a description of the options, see L<"bounces">.

=over 4

=item *

start_date

=item *

end_date

=item *

type

=item *

email

=back

To delete all bounces, call this without any options:

  $sendgrid->delete_bounces();

=head1 SEE ALSO

=over 4

=item L<Mail::SendGrid::Bounce>

The class which defines the data objects returned by the bounces method.

=item SendGrid API documentation

L<http://sendgrid.com/docs/API%20Reference/Web%20API/bounces.html>

=back

=head1 AUTHOR

Neil Bowers <neilb@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2012 by Neil Bowers <neilb@cpan.org>.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut