Repository: sendgrid/sendgrid-java
Pull request #621 (2020-05-05 20:22:12)

feat: add support for Twilio Email


Patch:
diff --git a/USE_CASES.md b/USE_CASES.md
index a77c941..520825d 100644
--- a/USE_CASES.md
+++ b/USE_CASES.md
@@ -5,8 +5,9 @@ This documentation provides examples for specific use cases. Please [open an iss
 * [Transactional Templates](#transactional-templates)
 * [Legacy Templates](#legacy-templates)
 * [How to Setup a Domain Authentication](#domain-authentication)
-* [How to View Email Statistics](#email-stats)
-* [Send a SMS Message](#sms)
+* [How to View Email Statistics](#how-to-view-email-statistics)
+* [Send an Email With Twilio Email (Pilot)](#send-an-email-with-twilio-email-pilot)
+* [Send an SMS Message](#send-an-sms-message)
 
 <a name="transactional-templates"></a>
 # Transactional Templates
@@ -225,84 +226,92 @@ You can find documentation for how to setup a domain authentication via the UI [
 
 Find more information about all of Twilio SendGrid's authentication related documentation [here](https://sendgrid.com/docs/ui/account-and-settings/).
 
-<a name="email-stats"></a>
 # How to View Email Statistics
 
 You can find documentation for how to view your email statistics via the UI [here](https://app.sendgrid.com/statistics) and via API [here](https://github.com/sendgrid/sendgrid-java/blob/master/USAGE.md#stats).
 
 Alternatively, we can post events to a URL of your choice via our [Event Webhook](https://sendgrid.com/docs/API_Reference/Webhooks/event.html) about events that occur as Twilio SendGrid processes your email.
 
-<a name="sms"></a>
-# Send a SMS Message
+# Send an Email With Twilio Email (Pilot)
 
-Following are the steps to add Twilio SMS to your app:
-
-## 1. Obtain a Free Twilio Account
+### 1. Obtain a Free Twilio Account
 
 Sign up for a free Twilio account [here](https://www.twilio.com/try-twilio?source=sendgrid-java).
 
-## 2. Update Your Environment Variables
+### 2. Set Up Your Environment Variables
+
+The Twilio API allows for authentication using with either an API key/secret or your Account SID/Auth Token. You can create an API key [here](https://twil.io/get-api-key) or obtain your Account SID and Auth Token [here](https://twil.io/console).
 
-You can obtain your Account Sid and Auth Token from [twilio.com/console](https://twilio.com/console).
+Once you have those, follow the steps below based on your operating system.
 
-### Mac
+#### Linux/Mac
 
 ```bash
+echo "export TWILIO_API_KEY='YOUR_TWILIO_API_KEY'" > twilio.env
+echo "export TWILIO_API_SECRET='YOUR_TWILIO_API_SECRET'" >> twilio.env
+
+# or
+
 echo "export TWILIO_ACCOUNT_SID='YOUR_TWILIO_ACCOUNT_SID'" > twilio.env
 echo "export TWILIO_AUTH_TOKEN='YOUR_TWILIO_AUTH_TOKEN'" >> twilio.env
+```
+
+Then:
+
+```bash
 echo "twilio.env" >> .gitignore
 source ./twilio.env
 ```
 
-### Windows
+#### Windows
 
 Temporarily set the environment variable (accessible only during the current CLI session):
 
 ```bash
+set TWILIO_API_KEY=YOUR_TWILIO_API_KEY
+set TWILIO_API_SECRET=YOUR_TWILIO_API_SECRET
+
+: or
+
 set TWILIO_ACCOUNT_SID=YOUR_TWILIO_ACCOUNT_SID
 set TWILIO_AUTH_TOKEN=YOUR_TWILIO_AUTH_TOKEN
 ```
 
-Permanently set the environment variable (accessible in all subsequent CLI sessions):
+Or permanently set the environment variable (accessible in all subsequent CLI sessions):
 
 ```bash
+setx TWILIO_API_KEY "YOUR_TWILIO_API_KEY"
+setx TWILIO_API_SECRET "YOUR_TWILIO_API_SECRET"
+
+: or
+
 setx TWILIO_ACCOUNT_SID "YOUR_TWILIO_ACCOUNT_SID"
 setx TWILIO_AUTH_TOKEN "YOUR_TWILIO_AUTH_TOKEN"
 ```
 
-## 3. Install the Twilio Helper Library
+### 3. Initialize the Twilio Email Client
 
-`twilio-java` uses Maven.  At present the jars *are* available from a public [maven](http://mvnrepository.com/artifact/com.twilio.sdk/twilio) repository.
+```java
+TwilioEmail mailClient = new TwilioEmail(System.getenv("TWILIO_API_KEY"), System.getenv("TWILIO_API_SECRET"));
 
-Use the following dependency in your project to grab via Maven:
+// or
 
-```xml
-<dependency>
-  <groupId>com.twilio.sdk</groupId>
-  <artifactId>twilio</artifactId>
-  <version>7.X.X</version>
-  <scope>compile</scope>
-</dependency>
+TwilioEmail mailClient = new TwilioEmail(System.getenv("TWILIO_ACCOUNT_SID"), System.getenv("TWILIO_AUTH_TOKEN"));
 ```
 
-or Gradle:
-```groovy
-compile "com.twilio.sdk:twilio:7.X.X"
-````
+This client has the same interface as the `SendGrid` client.
 
-If you want to compile it yourself, here is how:
+# Send an SMS Message
 
-```bash
-$ git clone git@github.com:twilio/twilio-java
-$ cd twilio-java
-$ mvn install       # Requires maven, download from http://maven.apache.org/download.html
-```
+First, follow the above steps for creating a Twilio account and setting up environment variables with the proper credentials.
+
+Then, install the Twilio Helper Library by following the [installation steps](https://github.com/twilio/twilio-java#installation).
 
-Then, you can execute the following code.
+Finally, send a message.
 
 ```java
-String accountSid = Environment.GetEnvironmentVariable("TWILIO_ACCOUNT_SID");
-String authToken = Environment.GetEnvironmentVariable("TWILIO_AUTH_TOKEN");
+String accountSid = System.getenv("TWILIO_ACCOUNT_SID");
+String authToken = System.getenv("TWILIO_AUTH_TOKEN");
 
 Twilio.init(accountSid, authToken);
 
@@ -315,4 +324,4 @@ Message message = Message.creator(
 System.out.println(message.getSid());
 ```
 
-For more information, please visit the [Twilio SMS Java documentation](https://www.twilio.com/docs/sms/quickstart/java).
\ No newline at end of file
+For more information, please visit the [Twilio SMS Java documentation](https://www.twilio.com/docs/sms/quickstart/java).
diff --git a/src/main/java/com/sendgrid/BaseInterface.java b/src/main/java/com/sendgrid/BaseInterface.java
new file mode 100644
index 0000000..0b87b52
--- /dev/null
+++ b/src/main/java/com/sendgrid/BaseInterface.java
@@ -0,0 +1,343 @@
+package com.sendgrid;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * This class is the base interface to the Twilio SendGrid Web API.
+ */
+public abstract class BaseInterface implements SendGridAPI {
+
+  private static final String VERSION = "3.0.0";
+
+  private static final String USER_AGENT = "sendgrid/" + VERSION + ";java";
+  private static final int RATE_LIMIT_RESPONSE_CODE = 429;
+  private static final int THREAD_POOL_SIZE = 8;
+
+  private ExecutorService pool;
+
+  /**
+   * The host to which to connect.
+   */
+  private String host;
+
+  /**
+   * The API version.
+   */
+  private String version;
+
+  /**
+   * The HTTP client.
+   */
+  private Client client;
+
+  /**
+   * The request headers container.
+   */
+  private Map<String, String> requestHeaders;
+
+  /**
+   * The number of times to try after a rate limit.
+   */
+  private int rateLimitRetry;
+
+  /**
+   * The number of milliseconds to sleep between retries.
+   */
+  private int rateLimitSleep;
+
+  /**
+   * The subuser to be impersonated.
+   */
+  private String subuser;
+
+  /**
+   * Construct a new API wrapper.
+   */
+  public BaseInterface() {
+    this.client = new Client();
+  }
+
+  /**
+   * Construct a new API wrapper.
+   *
+   * @param test is true if you are unit testing
+   */
+  public BaseInterface(final Boolean test) {
+    this.client = new Client(test);
+  }
+
+  /**
+   * Construct a new API wrapper.
+   *
+   * @param client the Client to use (allows to customize its configuration)
+   */
+  public BaseInterface(final Client client) {
+    this.client = client;
+  }
+
+  /**
+   * Initialize the client.
+   *
+   * @param auth authorization header value
+   * @param host the base URL for the API
+   */
+  public void initialize(final String auth, final String host) {
+    this.host = host;
+    this.version = "v3";
+    this.requestHeaders = new HashMap<>();
+    this.requestHeaders.put("Authorization", auth);
+    this.requestHeaders.put("User-Agent", USER_AGENT);
+    this.requestHeaders.put("Accept", "application/json");
+    this.rateLimitRetry = 5;
+    this.rateLimitSleep = 1100;
+
+    this.pool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
+  }
+
+  /**
+   * Get the current library version.
+   *
+   * @return the current version
+   */
+  public String getLibraryVersion() {
+    return VERSION;
+  }
+
+  /**
+   * Get the API version.
+   *
+   * @return the current API version
+   */
+  public String getVersion() {
+    return this.version;
+  }
+
+  /**
+   * Set the API version.
+   *
+   * @param version the new version
+   */
+  public void setVersion(final String version) {
+    this.version = version;
+  }
+
+  /**
+   * Get the request headers.
+   *
+   * @return the request headers
+   */
+  public Map<String, String> getRequestHeaders() {
+    return this.requestHeaders;
+  }
+
+  /**
+   * Add/update a request header.
+   *
+   * @param key   the header key
+   * @param value the header value
+   * @return the new set of request headers
+   */
+  public Map<String, String> addRequestHeader(final String key, final String value) {
+    this.requestHeaders.put(key, value);
+    return getRequestHeaders();
+  }
+
+  /**
+   * Remove a request header.
+   *
+   * @param key the header key to remove
+   * @return the new set of request headers
+   */
+  public Map<String, String> removeRequestHeader(final String key) {
+    this.requestHeaders.remove(key);
+    return getRequestHeaders();
+  }
+
+  /**
+   * Get the host.
+   *
+   * @return the host
+   */
+  public String getHost() {
+    return this.host;
+  }
+
+  /**
+   * Set the host.
+   *
+   * @param host the new host
+   */
+  public void setHost(final String host) {
+    this.host = host;
+  }
+
+  /**
+   * Get the maximum number of retries on a rate limit response.
+   * The default is 5.
+   *
+   * @return the number of retries on a rate limit
+   */
+  public int getRateLimitRetry() {
+    return this.rateLimitRetry;
+  }
+
+  /**
+   * Set the maximum number of retries on a rate limit response.
+   *
+   * @param rateLimitRetry the maximum retry count
+   */
+  public void setRateLimitRetry(final int rateLimitRetry) {
+    this.rateLimitRetry = rateLimitRetry;
+  }
+
+  /**
+   * Get the duration of time (in milliseconds) to sleep between
+   * consecutive rate limit retries. The Twilio SendGrid API enforces
+   * the rate limit to the second. The default value is 1.1 seconds.
+   *
+   * @return the sleep duration
+   */
+  public int getRateLimitSleep() {
+    return this.rateLimitSleep;
+  }
+
+  /**
+   * Set the duration of time (in milliseconds) to sleep between
+   * consecutive rate limit retries.
+   *
+   * @param rateLimitSleep the sleep duration
+   */
+  public void setRateLimitSleep(final int rateLimitSleep) {
+    this.rateLimitSleep = rateLimitSleep;
+  }
+
+  /**
+   * Impersonate subuser for subsequent requests
+   *
+   * @param subuser the subuser to be impersonated
+   */
+  public void addImpersonateSubuser(final String subuser) {
+    this.subuser = subuser;
+    this.addRequestHeader("on-behalf-of", subuser);
+  }
+
+  /**
+   * Stop Impersonating the subuser
+   */
+  public void removeImpersonateSubuser() {
+    this.subuser = null;
+    this.removeRequestHeader("on-behalf-of");
+  }
+
+  /**
+   * Get the impersonated subuser or null if empty
+   *
+   * @return the impersonated subuser
+   */
+  public String getImpersonateSubuser() {
+    return this.subuser;
+  }
+
+  /**
+   * Makes the call to the Twilio SendGrid API, override this method for testing.
+   *
+   * @param request the request to make
+   * @return the response object
+   * @throws IOException in case of a network error
+   */
+  public Response makeCall(final Request request) throws IOException {
+    return client.api(request);
+  }
+
+  /**
+   * Class api sets up the request to the Twilio SendGrid API, this is main interface.
+   *
+   * @param request the request object
+   * @return the response object
+   * @throws IOException in case of a network error
+   */
+  public Response api(final Request request) throws IOException {
+    final Request req = new Request();
+
+    req.setMethod(request.getMethod());
+    req.setBaseUri(this.host);
+    req.setEndpoint("/" + version + "/" + request.getEndpoint());
+    req.setBody(request.getBody());
+
+    for (final Map.Entry<String, String> header : this.requestHeaders.entrySet()) {
+      req.addHeader(header.getKey(), header.getValue());
+    }
+
+    for (final Map.Entry<String, String> queryParam : request.getQueryParams().entrySet()) {
+      req.addQueryParam(queryParam.getKey(), queryParam.getValue());
+    }
+
+    return makeCall(req);
+  }
+
+  /**
+   * Attempt an API call. This method executes the API call asynchronously
+   * on an internal thread pool. If the call is rate limited, the thread
+   * will retry up to the maximum configured time.
+   *
+   * @param request the API request
+   */
+  public void attempt(final Request request) {
+    this.attempt(request, new APICallback() {
+      @Override
+      public void error(final Exception ex) {
+      }
+
+      public void response(final Response r) {
+      }
+    });
+  }
+
+  /**
+   * Attempt an API call. This method executes the API call asynchronously
+   * on an internal thread pool. If the call is rate limited, the thread
+   * will retry up to the maximum configured time. The supplied callback
+   * will be called in the event of an error, or a successful response.
+   *
+   * @param request  the API request
+   * @param callback the callback
+   */
+  public void attempt(final Request request, final APICallback callback) {
+    this.pool.execute(new Runnable() {
+      @Override
+      public void run() {
+        Response response;
+
+        // Retry until the retry limit has been reached.
+        for (int i = 0; i < rateLimitRetry; ++i) {
+          try {
+            response = api(request);
+          } catch (IOException ex) {
+            // Stop retrying if there is a network error.
+            callback.error(ex);
+            return;
+          }
+
+          // We have been rate limited.
+          if (response.getStatusCode() == RATE_LIMIT_RESPONSE_CODE) {
+            try {
+              Thread.sleep(rateLimitSleep);
+            } catch (InterruptedException ex) {
+              // Can safely ignore this exception and retry.
+            }
+          } else {
+            callback.response(response);
+            return;
+          }
+        }
+
+        // Retries exhausted. Return error.
+        callback.error(new RateLimitException(request, rateLimitRetry));
+      }
+    });
+  }
+}
diff --git a/src/main/java/com/sendgrid/SendGrid.java b/src/main/java/com/sendgrid/SendGrid.java
index e746bcf..e30a00f 100644
--- a/src/main/java/com/sendgrid/SendGrid.java
+++ b/src/main/java/com/sendgrid/SendGrid.java
@@ -1,311 +1,47 @@
 package com.sendgrid;
 
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ExecutorService;
-
 /**
-  * Class Twilio SendGrid allows for quick and easy access to the Twilio SendGrid API.
-  */
-public class SendGrid implements SendGridAPI {
-
-  private static final String VERSION = "3.0.0";
-
-  /** The user agent string to return to Twilio SendGrid. */
-  private static final String USER_AGENT = "sendgrid/" + VERSION + ";java";
-  private static final int RATE_LIMIT_RESPONSE_CODE = 429;
-  private static final int THREAD_POOL_SIZE = 8;
-
-  private ExecutorService pool;
-
-  /** The Twilio SendGrid host to which to connect. */
-  private String host;
-
-  /** The API version. */
-  private String version;
-
-  /** The HTTP client. */
-  private Client client;
-
-  /** The request headers container. */
-  private Map<String, String> requestHeaders;
-
-  /** The number of times to try after a rate limit. */
-  private int rateLimitRetry;
-
-  /** The number of milliseconds to sleep between retries. */
-  private int rateLimitSleep;
-
-  /** The subuser to be impersonated. */
-  private String subuser;
+ * Class Twilio SendGrid allows for quick and easy access to the Twilio SendGrid API.
+ */
+public class SendGrid extends BaseInterface {
 
   /**
    * Construct a new Twilio SendGrid API wrapper.
+   *
    * @param apiKey is your Twilio SendGrid API Key: https://app.sendgrid.com/settings/api_keys
    */
-  public SendGrid(String apiKey) {
-    this.client = new Client();
+  public SendGrid(final String apiKey) {
     initializeSendGrid(apiKey);
   }
 
   /**
    * Construct a new Twilio SendGrid API wrapper.
+   *
    * @param apiKey is your Twilio SendGrid API Key: https://app.sendgrid.com/settings/api_keys
-   * @param test is true if you are unit testing
+   * @param test   is true if you are unit testing
    */
-  public SendGrid(String apiKey, Boolean test) {
-    this.client = new Client(test);
+  public SendGrid(final String apiKey, final Boolean test) {
+    super(test);
     initializeSendGrid(apiKey);
   }
 
   /**
    * Construct a new Twilio SendGrid API wrapper.
+   *
    * @param apiKey is your Twilio SendGrid API Key: https://app.sendgrid.com/settings/api_keys
    * @param client the Client to use (allows to customize its configuration)
    */
-  public SendGrid(String apiKey, Client client) {
-    this.client = client;
+  public SendGrid(final String apiKey, final Client client) {
+    super(client);
     initializeSendGrid(apiKey);
   }
 
   /**
    * Initialize the client.
-   * @param apiKey the user's API key.
-   */
-  public void initializeSendGrid(String apiKey) {
-    this.host = "api.sendgrid.com";
-    this.version = "v3";
-    this.requestHeaders = new HashMap<String, String>();
-    this.requestHeaders.put("Authorization", "Bearer " + apiKey);
-    this.requestHeaders.put("User-Agent", USER_AGENT);
-    this.requestHeaders.put("Accept", "application/json");
-    this.rateLimitRetry = 5;
-    this.rateLimitSleep = 1100;
-
-    this.pool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
-  }
-
-  /**
-   * Retrieve the current library version.
-   * @return the current version.
-   */
-  public String getLibraryVersion() {
-    return this.VERSION;
-  }
-
-  /**
-   * Get the API version.
-   * @return the current API versioin (v3 by default).
-   */
-  public String getVersion() {
-    return this.version;
-  }
-
-  /**
-   * Set the API version.
-   * @param version the new version.
+   *
+   * @param apiKey the user's API key
    */
-  public void setVersion(String version) {
-    this.version = version;
-  }
-
-  /**
-   * Obtain the request headers.
-   * @return the request headers.
-   */
-  public Map<String, String> getRequestHeaders() {
-    return this.requestHeaders;
-  }
-
-  /**
-   * Add a new request header.
-   * @param key the header key.
-   * @param value the header value.
-   * @return the new set of request headers.
-   */
-  public Map<String, String> addRequestHeader(String key, String value) {
-    this.requestHeaders.put(key, value);
-    return getRequestHeaders();
-  }
-
-  /**
-   * Remove a request header.
-   * @param key the header key to remove.
-   * @return the new set of request headers.
-   */
-  public Map<String, String> removeRequestHeader(String key) {
-    this.requestHeaders.remove(key);
-    return getRequestHeaders();
-  }
-
-  /**
-   * Get the Twilio SendGrid host (api.sendgrid.com by default).
-   * @return the Twilio SendGrid host.
-   */
-  public String getHost() {
-    return this.host;
-  }
-
-  /**
-   * Set the Twilio SendGrid host.
-   * @param host the new Twilio SendGrid host.
-   */
-  public void setHost(String host) {
-    this.host = host;
-  }
-
-  /**
-   * Get the maximum number of retries on a rate limit response.
-   * The default is 5.
-   * @return the number of retries on a rate limit.
-   */
-  public int getRateLimitRetry() {
-    return this.rateLimitRetry;
-  }
-
-  /**
-   * Set the maximum number of retries on a rate limit response.
-   * @param rateLimitRetry the maximum retry count.
-   */
-  public void setRateLimitRetry(int rateLimitRetry) {
-    this.rateLimitRetry = rateLimitRetry;
-  }
-
-  /**
-   * Get the duration of time (in milliseconds) to sleep between
-   * consecutive rate limit retries. The Twilio SendGrid API enforces
-   * the rate limit to the second. The default value is 1.1 seconds.
-   * @return the sleep duration.
-   */
-  public int getRateLimitSleep() {
-    return this.rateLimitSleep;
-  }
-
-  /**
-   * Set the duration of time (in milliseconds) to sleep between
-   * consecutive rate limit retries.
-   * @param rateLimitSleep the sleep duration.
-   */
-  public void setRateLimitSleep(int rateLimitSleep) {
-    this.rateLimitSleep = rateLimitSleep;
-  }
-
-  /**
-   * Impersonate subuser for subsequent requests
-   * @param subuser the subuser to be impersonated
-   */
-  public void addImpersonateSubuser(String subuser) {
-    this.subuser = subuser;
-    this.addRequestHeader("on-behalf-of", subuser);
-  }
-
-  /**
-   * Stop Impersonating the subuser
-   */
-  public void removeImpersonateSubuser() {
-    this.subuser = null;
-    this.removeRequestHeader("on-behalf-of");
-  }
-
-  /**
-   * Get the impersonated subuser or null if empty
-   * @return the impersonated subuser
-   */
-  public String getImpersonateSubuser() {
-    return this.subuser;
-  }
-
-  /**
-   * Makes the call to the Twilio SendGrid API, override this method for testing.
-   * @param request the request to make.
-   * @return the response object.
-   * @throws IOException in case of a network error.
-   */
-  public Response makeCall(Request request) throws IOException {
-    return client.api(request);
-  }
-
-  /**
-   * Class api sets up the request to the Twilio SendGrid API, this is main interface.
-   * @param request the request object.
-   * @return the response object.
-   * @throws IOException in case of a network error.
-   */
-  public Response api(Request request) throws IOException {
-    Request req = new Request();
-    req.setMethod(request.getMethod());
-    req.setBaseUri(this.host);
-    req.setEndpoint("/" + version + "/" + request.getEndpoint());
-    req.setBody(request.getBody());
-    for (Map.Entry<String, String> header : this.requestHeaders.entrySet()) {
-      req.addHeader(header.getKey(), header.getValue());
-    }
-    for (Map.Entry<String, String> queryParam : request.getQueryParams().entrySet()) {
-      req.addQueryParam(queryParam.getKey(), queryParam.getValue());
-    }
-
-    return makeCall(req);
-  }
-
-  /**
-   * Attempt an API call. This method executes the API call asynchronously
-   * on an internal thread pool. If the call is rate limited, the thread
-   * will retry up to the maximum configured time.
-   * @param request the API request.
-   */
-  public void attempt(Request request) {
-    this.attempt(request, new APICallback() {
-      @Override
-      public void error(Exception ex) {
-      }
-
-      public void response(Response r) {
-      }
-    });
-  }
-
-  /**
-   * Attempt an API call. This method executes the API call asynchronously
-   * on an internal thread pool. If the call is rate limited, the thread
-   * will retry up to the maximum configured time. The supplied callback
-   * will be called in the event of an error, or a successful response.
-   * @param request the API request.
-   * @param callback the callback.
-   */
-  public void attempt(final Request request, final APICallback callback) {
-    this.pool.execute(new Runnable() {
-      @Override
-      public void run() {
-        Response response;
-
-        // Retry until the retry limit has been reached.
-        for (int i = 0; i < rateLimitRetry; ++i) {
-          try {
-            response = api(request);
-          } catch (IOException ex) {
-            // Stop retrying if there is a network error.
-            callback.error(ex);
-            return;
-          }
-
-          // We have been rate limited.
-          if (response.getStatusCode() == RATE_LIMIT_RESPONSE_CODE) {
-            try {
-              Thread.sleep(rateLimitSleep);
-            } catch (InterruptedException ex) {
-              // Can safely ignore this exception and retry.
-            }
-          } else {
-            callback.response(response);
-            return;
-          }
-        }
-
-        // Retries exhausted. Return error.
-        callback.error(new RateLimitException(request, rateLimitRetry));
-      }
-    });
+  public void initializeSendGrid(final String apiKey) {
+    this.initialize("Bearer " + apiKey, "api.sendgrid.com");
   }
 }
diff --git a/src/main/java/com/sendgrid/SendGridAPI.java b/src/main/java/com/sendgrid/SendGridAPI.java
index aa9583a..71b2c3c 100644
--- a/src/main/java/com/sendgrid/SendGridAPI.java
+++ b/src/main/java/com/sendgrid/SendGridAPI.java
@@ -6,86 +6,87 @@ import java.util.Map;
 public interface SendGridAPI {
 
   /**
-   * Initializes Twilio SendGrid
-   * 
-   * @param apiKey is your Twilio SendGrid API Key: https://app.sendgrid.com/settings/api_keys
+   * Initialize the client.
+   *
+   * @param auth authorization header value
+   * @param host the base URL for the API
    */
-  void initializeSendGrid(String apiKey);
+  void initialize(final String auth, final String host);
 
   /**
-   * Returns the library version
-   * 
-   * @return the library version.
+   * Get the current library version.
+   *
+   * @return the current version
    */
   String getLibraryVersion();
 
   /**
-   * Gets the version.
+   * Get the API version.
    *
-   * @return returns the version.
+   * @return the current API version
    */
   String getVersion();
 
   /**
-   * Sets the version.
-   * 
-   * @param version the Twilio SendGrid version.
+   * Set the API version.
+   *
+   * @param version the new version
    */
-  void setVersion(String version);
+  void setVersion(final String version);
 
   /**
-   * Gets the request headers.
-   * @return returns a map of request headers.
+   * Get the request headers.
+   *
+   * @return the request headers
    */
   Map<String, String> getRequestHeaders();
 
   /**
-   * Adds a request headers.
-   * 
-   * @param key the key
-   * @param value the value
-   * @return returns a map of request headers.
+   * Add/update a request header.
+   *
+   * @param key   the header key
+   * @param value the header value
+   * @return the new set of request headers
    */
-  Map<String, String> addRequestHeader(String key, String value);
+  Map<String, String> addRequestHeader(final String key, final String value);
 
   /**
-   * Removes a request headers.
-   * 
-   * @param key the key
-   * @return returns a map of request headers.
+   * Remove a request header.
+   *
+   * @param key the header key to remove
+   * @return the new set of request headers
    */
-  Map<String, String> removeRequestHeader(String key);
+  Map<String, String> removeRequestHeader(final String key);
 
   /**
-   * Gets the host.
-   * 
-   * @return returns the host.
+   * Get the host.
+   *
+   * @return the host
    */
   String getHost();
 
   /**
-   * Sets the host.
-   * 
-   * @param host the host to set
+   * Set the host.
+   *
+   * @param host the new host
    */
-  void setHost(String host);
+  void setHost(final String host);
 
   /**
-   * Class makeCall makes the call to the Twilio SendGrid API, override this method for
-   * testing.
-   * 
-   * @param request the request
-   * @return returns a response.
-   * @throws IOException in case of network or marshal error.
+   * Makes the call to the Twilio SendGrid API, override this method for testing.
+   *
+   * @param request the request to make
+   * @return the response object
+   * @throws IOException in case of a network error
    */
-  Response makeCall(Request request) throws IOException;
+  Response makeCall(final Request request) throws IOException;
 
   /**
    * Class api sets up the request to the Twilio SendGrid API, this is main interface.
-   * 
-   * @param request the request
-   * @return returns a response.
-   * @throws IOException in case of network or marshal error.
+   *
+   * @param request the request object
+   * @return the response object
+   * @throws IOException in case of a network error
    */
-  Response api(Request request) throws IOException;
+  Response api(final Request request) throws IOException;
 }
diff --git a/src/main/java/com/sendgrid/TwilioEmail.java b/src/main/java/com/sendgrid/TwilioEmail.java
new file mode 100644
index 0000000..d2c7950
--- /dev/null
+++ b/src/main/java/com/sendgrid/TwilioEmail.java
@@ -0,0 +1,50 @@
+package com.sendgrid;
+
+import org.apache.commons.codec.binary.Base64;
+
+/**
+ * This class allows you to quickly and easily send emails through Twilio
+ * SendGrid using Java.
+ */
+public class TwilioEmail extends BaseInterface {
+
+  /**
+   * Construct a new Twilio SendGrid API wrapper.
+   *
+   * @param username your Twilio Email API key SID or Account SID
+   * @param password your Twilio Email API key secret or Account Auth Token
+   */
+  public TwilioEmail(final String username, final String password) {
+    initialize(username, password);
+  }
+
+  /**
+   * Construct a new Twilio SendGrid API wrapper.
+   *
+   * @param username your Twilio Email API key SID or Account SID
+   * @param password your Twilio Email API key secret or Account Auth Token
+   * @param client   the Client to use (allows to customize its configuration)
+   */
+  public TwilioEmail(final String username, final String password, final Client client) {
+    super(client);
+    initialize(username, password);
+  }
+
+  /**
+   * Initialize the client.
+   *
+   * @param username your Twilio Email API key SID or Account SID
+   * @param password your Twilio Email API key secret or Account Auth Token
+   */
+  public void initialize(final String username, final String password) {
+    super.initialize(getAuthString(username, password), "email.twilio.com");
+  }
+
+  private String getAuthString(final String username, final String password) {
+    final String credentials = username + ":" + password;
+    final byte[] encodedBytes = Base64.encodeBase64(credentials.getBytes());
+    final String encoderString = new String(encodedBytes);
+
+    return "Basic " + encoderString;
+  }
+}


Test patch:
diff --git a/src/test/java/com/sendgrid/TwilioEmailTest.java b/src/test/java/com/sendgrid/TwilioEmailTest.java
new file mode 100644
index 0000000..e07c2ca
--- /dev/null
+++ b/src/test/java/com/sendgrid/TwilioEmailTest.java
@@ -0,0 +1,29 @@
+package com.sendgrid;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class TwilioEmailTest {
+
+  @Test
+  public void testInitialization() {
+    final TwilioEmail sg = new TwilioEmail("username", "password");
+    Assert.assertEquals("email.twilio.com", sg.getHost());
+    Assert.assertEquals("Basic dXNlcm5hbWU6cGFzc3dvcmQ=", sg.getRequestHeaders().get("Authorization"));
+  }
+
+  @Test
+  public void testConstructWithClient() throws IOException {
+    final Client client = mock(Client.class);
+    final TwilioEmail sg = new TwilioEmail("username", "password", client);
+    final Request request = new Request();
+
+    sg.makeCall(request);
+    verify(client).api(request);
+  }
+}