Rackspace is a great email hosting company, providing, amongst other things, a handy API for creating bespoke email solutions. The exercise of integrating that API into your application is of course left to the end user. I’ve spent some time working on a Rackspace API library for the PHP programming framework, CodeIgniter. This is not functionally complete – I have only implemented the interfaces that I needed – but it should provide a useful springboard for your own projects.
Configuration
In /system/application/config/RackspaceAPI.php:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
$config['user_key'] = 'your user key';
$config['secret_key'] = 'your secret key';
$config['user_agent'] = 'name of your app';
$config['api_version'] = 'v0'; // amend if necessary
$config['rackspace_host'] = 'api.emailsrvr.com'; // amend if necessary
/* End of file RackspaceAPI.php */
/* Location: ./system/application/config/RackspaceAPI.php */
Library
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
* Uses curl and pecl_http
*/
class Rackspace_API {
/**
* Store recent http_message
* @var object
*/
protected $_http_message;
/**
* CI object
* @var object
*/
protected $_ci;
/**
* Rackspace config items
*/
protected $_user_key;
protected $_secret_key;
protected $_user_agent;
protected $_api_version;
protected $_rackspace_host;
function __construct() {
$this->_ci =& get_instance();
$this->_ci->config->load('RackspaceAPI', TRUE);
$this->_user_key = $this->_ci->config->item('user_key', 'RackspaceAPI');
$this->_secret_key = $this->_ci->config->item('secret_key', 'RackspaceAPI');
$this->_user_agent = $this->_ci->config->item('user_agent', 'RackspaceAPI');
$this->_api_version = $this->_ci->config->item('api_version', 'RackspaceAPI');
$this->_rackspace_host = $this->_ci->config->item('rackspace_host', 'RackspaceAPI');
}
/**
* Get info about a domain
* @param string $domain
* @return stdClass Object ( 'error' => bool,
* 'result' => string (error message) | stdClass Object
*/
public function getDomainInfo($domain) {
return $this->genericGet('/customers/me/domains/'.$domain);
}
/**
* Get all domain names
* @return stdClass Object ( 'error' => bool,
* 'result' => string (error message) | array (domains)
*/
public function getDomains() {
$obj = $this->genericGet('/customers/me/domains');
if(!$obj->error){
// Reformat into an array of domains
foreach($obj->result->domains as $domain) {
$domains[]=$domain->name;
}
$obj->result = $domains;
}
return $obj;
}
/**
* Get info about a mailbox ($domain@$id)
* @param string $domain
* @param string $id
* @return stdClass Object ( 'error' => bool,
* 'result' => string (error message) | stdClass Object
*/
public function getMailboxInfo($domain, $id) {
return $this->genericGet('/customers/me/domains/'.$domain.'/rs/mailboxes/'.$id);
}
/**
* Used by Get functions above - generalised use case
* @param string $url - see the API; constructed by the calling function
* @return stdClass Object ( 'error' => bool,
* 'result' => string (error message) | stdClass Object
*/
private function genericGet($url) {
$this->get(
$url,
'application/json');
if($this->_http_message->getResponseCode() == 200) {
// Call worked. JSON is missing enclosing brackets, apparently needed by json_decode
$json = '['.$this->_http_message->getBody().']';
if(is_string($json)) {
$obj = json_decode($json);
$result->error = false;
$result->result = $obj[0];
} else {
// JSON failure
$result->error = true;
$result->result = 'Failed to parse JSON';
}
} else {
// API call failed
$result->error = true;
$result->result = $this->_http_message->getHeader("x-error-message");
}
return $result;
}
/**
* Create a mailbox ($domain@$id)
* @param string $domain
* @param string $id
* @param string $first: First name
* @param string $last: Last name
* @param string $name: Display as
* @param string $office: Name of office/profit centre
* @param string $locno: Office/profit centre number
* @param string $password
* @param string $fwd: comma-separated forwarding address(es) - max 4 off domain
* @param string $save: save forwarded email - 'true' or 'false'
* saveForwardedEmail
* @return stdClass Object ( 'error' => bool,
* 'result' => string (error message) | stdClass Object
*/
public function addMailbox($domain, $id, $first, $last, $name, $office,
$locno, $password, $fwd, $save='true') {
$fields = array(
'password' => $password,
'emailForwardingAddresses' => $fwd,
'firstName' => $first,
'lastName' => $last,
'displayName' => $name,
'saveForwardedEmail' => $save,
'organization' => $office,
'organizationUnit' => $locno);
return $this->genericPost( '/customers/me/domains/'.$domain.'/rs/mailboxes/'.$id, $fields);
}
/**
* Used by Post functions above - generalised use case
* Note: Rackspace API suggests use POST to add, PUT to edit
* @param string $url - see the API; constructed by the calling function
* @param array $fields - data to be POSTed
* @return stdClass Object ( 'error' => bool,
* 'result' => string (error message) | stdClass Object
*/
private function genericPost($url, $fields) {
$this->post(
$url,
$fields,
'application/json');
if($this->_http_message->getResponseCode() == 200) {
$result->error = false;
$result->result = $this->_http_message->getBody();
} else {
// API call failed
$result->error = true;
$result->result = $this->_http_message->getHeader("x-error-message");
}
return $result;
}
/**
* Edit user's forwarding
* @param string $domain
* @param string $id
* @param string $fwd: comma-separated forwarding address(es) - max 4 off domain
* @return stdClass Object ( 'error' => bool,
* 'result' => string (error message) | stdClass Object
*/
public function changeForwarding($domain, $id, $fwd) {
$fields = array(
'emailForwardingAddresses' => $fwd
);
return $this->genericPut( '/customers/me/domains/'.$domain.'/rs/mailboxes/'.$id, $fields);
}
/**
* Edit user's location
* @param string $domain
* @param string $id
* @param string $office: Name of office/profit centre
* @param string $locno: Office/profit centre number
* @return stdClass Object ( 'error' => bool,
* 'result' => string (error message) | stdClass Object
*/
public function changeLocation($domain, $id, $office, $locno) {
$fields = array(
'organization' => $office,
'organizationUnit' => $locno);
return $this->genericPut( '/customers/me/domains/'.$domain.'/rs/mailboxes/'.$id, $fields);
}
/**
* Edit user's name
* @param string $domain
* @param string $id
* @param string $first: First name
* @param string $last: Last name
* @param string $name: Display as
* @return stdClass Object ( 'error' => bool,
* 'result' => string (error message) | stdClass Object
*/
public function changeName($domain, $id, $first, $last, $name) {
$fields = array(
'firstName' => $first,
'lastName' => $last,
'displayName' => $name);
return $this->genericPut( '/customers/me/domains/'.$domain.'/rs/mailboxes/'.$id, $fields);
}
/**
* Edit user's password
* @param string $domain
* @param string $id
* @param string $password
* @return stdClass Object ( 'error' => bool,
* 'result' => string (error message) | stdClass Object
*/
public function changePassword($domain, $id, $password) {
$fields = array(
'password' => $password);
return $this->genericPut( '/customers/me/domains/'.$domain.'/rs/mailboxes/'.$id, $fields);
}
/**
* Used by Put functions above - generalised use case
* Note: Rackspace API suggests use PUT to edit, POST to add
* @param string $url - see the API; constructed by the calling function
* @param array $fields - data to be PUT
* @return stdClass Object ( 'error' => bool,
* 'result' => string (error message) | stdClass Object
*/
private function genericPut($url, $fields) {
$this->put(
$url,
$fields);
if($this->_http_message->getResponseCode() == 200) {
$result->error = false;
$result->result = $this->_http_message->getBody();
} else {
// API call failed
$result->error = true;
$result->result = $this->_http_message->getHeader("x-error-message");
}
return $result;
}
/**
* Delete a mailbox
* @param string $domain
* @param string $id
* @return stdClass Object ( 'error' => bool,
* 'result' => string (error message) | stdClass Object
*/
public function deleteMailbox($domain, $id) {
return $this->genericDelete("/customers/me/domains/$domain/rs/mailboxes/$id");
}
/**
* Used by Get functions above - generalised use case
* @param string $url - see the API; constructed by the calling function
* @return stdClass Object ( 'error' => bool,
* ['result' => string (error message)]
*/
private function genericDelete($url) {
$this->delete($url);
if($this->_http_message->getResponseCode() == 200) {
// Call worked.
$result->error = false;
} else {
if($this->_http_message->getResponseCode() == 500) {
// Internal server error
$result->error = true;
$result->result = 'An internal server error occurred deleting object. Url: '.$url;
} else {
// API call failed
$result->error = true;
$result->result = $this->_http_message->getHeader("x-error-message");
}
}
return $result;
}
// The remainder of this file is mostly lifted from Rackspace's examples: http://api-wiki.apps.rackspace.com/api-wiki/index.php/PHP_Examples_(Rest_API)
private function get($url_string, $format) {
$headers = array("Accept: $format");
$curl_session = self::construct_session($url_string, $headers);
$this->_http_message = self::send_request($curl_session);
}
private function post($url_string, $fields, $format) {
$headers = array("Accept: $format");
$curl_session = self::construct_session($url_string, $headers);
curl_setopt($curl_session, CURLOPT_POST, true);
curl_setopt($curl_session, CURLOPT_POSTFIELDS, $fields);
$this->_http_message = self::send_request($curl_session);
}
private function put($url_string, $fields) {
$curl_session = self::construct_session($url_string, array());
curl_setopt($curl_session, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($curl_session, CURLOPT_POSTFIELDS, $fields);
$this->_http_message = self::send_request($curl_session);
}
private function delete($url_string) {
$curl_session = self::construct_session($url_string, array());
curl_setopt($curl_session, CURLOPT_CUSTOMREQUEST, 'DELETE');
$this->_http_message = self::send_request($curl_session);
}
private function send_request($curl_session) {
$response = curl_exec($curl_session);
curl_close($curl_session);
/* Reponse string may contain two HTTP sessions, if there was an initial
"HTTP/1.1 100 Continue" response. So strip that first response out. Eg:
HTTP/1.1 100 Continue
Via: 1.1 [proxy]
HTTP/1.1 400 Bad Request
Via: 1.1 [proxy]
Connection: Keep-Alive
Proxy-Connection: Keep-Alive
...
*
*/
$response = preg_replace('|HTTP/1.1 100.*HTTP/1.1|isU', 'HTTP/1.1', $response);
return new HttpMessage($response);
}
private function construct_session($url_string, $existing_headers) {
$headers = array_merge(
self::authorization_headers(), $existing_headers);
$url = self::construct_uri($url_string);
$curl_session = curl_init($url);
curl_setopt($curl_session, CURLOPT_HEADER, true);
curl_setopt($curl_session, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl_session, CURLOPT_RETURNTRANSFER, true);
return $curl_session;
}
private function authorization_headers() {
$time_stamp = date('YmdHis');
$data_to_sign = $this->_user_key . $this->_user_agent .
$time_stamp. $this->_secret_key;
$signature = base64_encode(sha1($data_to_sign, true));
$headers = array();
$headers[] = "User-Agent: " . $this->_user_agent;
$headers[] = 'X-Api-Signature: ' .
$this->_user_key . ":$time_stamp:$signature";
return $headers;
}
private function construct_uri($url_string) {
$url = 'http://' . $this->_rackspace_host . '/' . $this->_api_version . $url_string;
return $url;
}
}
?>
Example
Example usage:
function testRackspace() {
$this->load->library('Rackspace_API');
$client = new Rackspace_API();
$obj = $client->getMailboxInfo('somedomain.com', 'test.user');
if($obj->error) {
echo 'Error: '.$obj->result;
} else {
var_dump($obj);
}
}
Image copyright © Rackspace Ltd. All rights acknowledged.