Using SendGrid for Email

February 4, 2013 under Email, PHP

Email can be a pain in the gazoo. Your code works, you've set up dovecot and postfix correctly, and email recipients... are not receiving emails from your application. The most common cause of failure, of course, is spam filters. Depending on the nature of the spam filter, the email might be automatically deleted!

SendGrid

The key to avoiding this sort of problem is ensuring that your emails never get classified as spam. There are many services that help with this, but the one I selected was SendGrid. Signup was a breeze. They have two different APIs, one for using SMTP and one using web services. SendGrid recommends using SMTP, but I want to get away from having email on my servers at all.

The only difficulty in using SendGrid was their PHP documentation (http://sendgrid.com/docs/Code_Examples/php.html), which is simply wrong. The following line is what they show as the last step in sending an email using their object library using web or smtp services.

$sendgrid->web->send($mail);
$sendgrid->smtp->send($mail);

The problem is that the web and smtp properties do not exist, which results in a fatal error when you try to send web or smtp a send() message.

Making SendGrid work

I can only guess that SendGrid's documentation applied to an earlier version of their library. In any case, after spending a bit of quality time with their code, I was able to make it work.

<?php

// In a configuration file
define('SENDGRID_ROOT', '/Users/markf/Source/sendgrid/Sendgrid_loader.php');
define('SENDGRID_USERNAME', 'myusername');
define('SENDGRID_PASSWORD', 'mypassword');

include SENDGRID_ROOT;

// Create the SendGrid mail object
$email = new SendGrid\Mail();
$email->addTo('recipient@example.com')
      ->setFrom('sender@istarelworkshop.com')
      ->setSubject('Important Application Notice')
      ->setText('This is a plain text message')
      ->setHtml('<p>This is an HTML message</p>');

// Use the SendGrid Web object to send the email
$web = new SendGrid\Web(SENDGRID_USERNAME, SENDGRID_PASSWORD);
$web->send($email);

?>

Create a URL shortener

December 3, 2012 under PHP, PostgreSQL

URL shorteners are all the rage. At their core, they have two functions. First, encode an existing complete URL. Second, decode a shortened version of a URL and redirect to the (restored) URL.

Obviously, there are a number of services already available to do this for you, but I was very interested in adapting my framework to a very different kind of application. Since the framework uses a front controller design pattern, I can put almost all the key logic in the ApplicationDelegate object.

So, the first steps are to create a database to handle the data, and an application directory to handle the logic.

psql template1 postgres
CREATE USER iwurl WITH PASSWORD 'dbpassword' CREATEDB;
\q
createdb -U iwurl --encoding UNICODE --template template0 url
cd ~/Sites/fw/install
php app_install -n URL Shortener -w /Users/markf/Sites -a url -d pgsql:url@localhost:iwurl:dbpassword

I am going to adapt the ApplicationDelegate to respond to the two different possible requests.

Database Schema

For the moment, I am going to ignore anything but the actual shortening and expanding. Eventually, the schema will include tables for managing demographics and usage.

CREATE TABLE url (
   url_id serial,
   short varchar(5),
   long text
);
ALTER TABLE url ADD CONSTRAINT pkey_url PRIMARY KEY (url_id);
cd ~/Sites/fw/install
php orm_install -d -p -n URL Shortener -w /Users/markf/Sites -a url -r url

Front Controller

The processing can be entirely handled in the ApplicationDelegate object. Whenever a request is received, the framework gives the application an opportunity to intercede. Often, this means taking an SEO-friendly URL and turning it into an application module name (sometimes with translated parameters). For this example, however, either a request is being made to shorten a URL, or to take a shortened URL and redirect to its expanded URL. This can be done by updating the modifyPageRequest() method of ApplicationDelegate.

function modifyPageRequest($url)
{
   // Performing a url shortening
   if ($url == 'shorten') return 'Shorten';
   
   // Redirecting to the appropriate place
   $url = UrlFactory::retrieveUrl($url, 'short');
   $destination = $url ? $url->long : APPL_ROOT_DIR . 'error.html';      
   header('Location: ' . $destination);
   exit();
}

Shorten System

To shorten a URL, I need to create a new entry in the url table.

class Shorten extends IWRenderModule
{
   protected $long;
   
   // can be passed via post or get (for now)
   function initialize($module)
   {
      if (! $this->long = IWRequest::postValue('url'))
      {
         $this->long = IWRequest::getValue('url');
         if ($this->long) $this->long = urldecode($this->long);
      }
   }
   
   function output()
   {
      if (! $this->long) return ApplicationDelegate::ERROR;    // constant set to -1
      
      return UrlFactory::createShortenedURL($this->long);
   }
}

class UrlFactory extends DefaultUrlFactory
{
   public static function createShortenedURL($long)
   {
      // If URL already encoded, return its short version
      if ($url = UrlFactory::retrieveUrl($long, 'long')) return 'http://istrl.ws/' . $url->short;
      
      // Create a new URL
      $url = new Url;
      $url->setShort(IWCodeMaker::makeCode('@@@@@'))
          ->setLong($long);
      $url->insert();
      
      return 'http://istrl.ws/' . $url->short;
   }
}

The IWCodeMaker object (part of the Istarel Workshop Application Framework) is a handy utility object for creating codes (including temporary passwords). In this case, we're creating a five character smart alphanumeric code (the non-zero numerals and non-confusing letters).

And that is a simple implementation of a URL shortening service. Of course, there is a lot of potential work to be done, including validation of the url, preventing duplicates, implementing the whole process via RESTful services, and so on. Those will be topics of future blog entries.

PHP Sendmail under Mountain Lion

October 9, 2012 under Mac OS X, PHP

With an upgrade to Mac OS X 10.8 (Mountain Lion), PHP's sendmail() inexplicably ceased working. I knew that a number of things got re-defaulted (like /etc/paths) but this seemed a bit different.

The telltale error message in your Apache log is:

sendmail: fatal: chdir /Library/Server/Mail/Data/spool: No such file or directory

To fix this problem, simply do:

sudo mkdir -p /Library/Server/Mail/Data/spool
sudo /usr/sbin/postfix set-permissions
sudo /usr/sbin/postfix start

Please note that you will see some warnings when you execute those commands, but it will work, I promise!

References

  • http://apple.stackexchange.com/questions/54051/sendmail-error-on-os-x-mountain-lion
  • http://stackoverflow.com/questions/11696609/php-mail-no-longer-works-after-update-to-osx-mountain-lion