diff --git a/includes/WebformScheduledExportConfig.php b/includes/WebformScheduledExportConfig.php
index 366992e..fac5632 100644
--- a/includes/WebformScheduledExportConfig.php
+++ b/includes/WebformScheduledExportConfig.php
@@ -49,6 +49,10 @@ class WebformScheduledExportConfig extends \Entity {
         case 'monthly':
           $send_date += $one_day * 30;
           break;
+
+        case 'calendar_month':
+          $send_date = webform_scheduled_export_increment_by_calendar_month($send_date);
+          break;
       }
       $this->setSendDate($send_date);
       $this->setDelivered(FALSE);
diff --git a/tests/webform_calendar_month_scheduled_export.test b/tests/webform_calendar_month_scheduled_export.test
new file mode 100644
index 0000000..b423eb7
--- /dev/null
+++ b/tests/webform_calendar_month_scheduled_export.test
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * @file
+ * Test Webform calendar month scheduled export.
+ */
+
+require_once 'webform_scheduled_export_base.test';
+
+class WebformCalendarMonthScheduledExportTest extends WebformScheduledExportBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => t('Webform Calendar Month Scheduled Export'),
+      'description' => t('Tests the calendar month webform scheduled export functionality'),
+      'group' => t('Webform Scheduled Export'),
+    );
+  }
+
+  /**
+   * Test the calendar month is validated to run on the 28th or less.
+   */
+  public function testCalendarMonthValidation() {
+    $node = $this->createWebformAndSubmission();
+    $mail = $this->randomName() . '@example.com';
+
+    $url = sprintf('node/%s/webform-results/scheduled-export/add', $node->nid);
+    $this->drupalPost($url, array(
+      'email' => $mail,
+      'frequency' => 'calendar_month',
+      'send_date[date]' => '29/05/2016',
+      'send_date[time]' => '14:30',
+      'delivery_method' => 'WseEmailNotification',
+    ), 'Save export configuration');
+    $this->assertText('you must select the 28th or less for any given month');
+
+    $this->drupalPost(NULL, array(
+      'email' => $mail,
+      'frequency' => 'calendar_month',
+      'send_date[date]' => '28/05/2016',
+      'send_date[time]' => '14:30',
+      'delivery_method' => 'WseEmailNotification',
+    ), 'Save export configuration');
+    $this->assertText('Webform scheduled export has been created.');
+  }
+
+  /**
+   * Test the time scheduling of the calendar month.
+   */
+  public function testCalendarMonthSchedule() {
+    $node = $this->createWebformAndSubmission();
+    $mail = $this->randomName() . '@example.com';
+
+    $this->drupalPost(sprintf('node/%s/webform-results/scheduled-export/add', $node->nid), array(
+      'email' => $mail,
+      'frequency' => 'calendar_month',
+      'send_date[date]' => '28/05/2016',
+      'send_date[time]' => '14:30',
+      'delivery_method' => 'WseEmailNotification',
+    ), 'Save export configuration');
+    $this->assertText('Webform scheduled export has been created.');
+
+    $this->assertDateExportSequence($node->nid, array(
+      'Sat, 05/28/2016 - 14:30',
+      'Tue, 06/28/2016 - 14:30'
+    ));
+  }
+
+  /**
+   * Assert the sequence of dates produced by consecutive exports.
+   */
+  protected function assertDateExportSequence($nid, $dates) {
+    $this->drupalGet(sprintf('node/%s/webform-results/scheduled-export', $nid));
+
+    foreach ($dates as $date) {
+      $this->assertText($date);
+      $this->cronRun();
+      $this->drupalGet(sprintf('node/%s/webform-results/scheduled-export', $nid));
+    }
+  }
+
+}
diff --git a/tests/webform_scheduled_export.test b/tests/webform_scheduled_export.test
index 1c2aaa0..1c10d53 100644
--- a/tests/webform_scheduled_export.test
+++ b/tests/webform_scheduled_export.test
@@ -5,27 +5,9 @@
  * Test Webform scheduled export.
  */
 
-class WebformScheduleExportTest extends DrupalWebTestCase {
+require_once 'webform_scheduled_export_base.test';
 
-  /**
-   * The profile to install as a basis for testing.
-   *
-   * @var string
-   */
-  protected $profile = 'testing';
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp() {
-    parent::setUp(array('webform_scheduled_export'));
-    $account = $this->drupalCreateUser(array(
-      'bypass node access',
-      'edit webform components',
-      'access all webform results',
-    ));
-    $this->drupalLogin($account);
-  }
+class WebformScheduleExportTest extends WebformScheduledExportBase {
 
   /**
    * {@inheritdoc}
@@ -344,69 +326,4 @@ class WebformScheduleExportTest extends DrupalWebTestCase {
     $this->assertEmails(array($mail1, $mail2));
   }
 
-  /**
-   * Configure our export.
-   */
-  protected function configureExport($nid, $emails, $frequency, $date, $time, $extra = array(), $edit_existing = TRUE) {
-    if ($edit_existing && $existing_export = WebformScheduledExportConfig::loadByWebformNid($nid)) {
-      $existing_export = array_shift($existing_export);
-      $url = sprintf('node/%s/webform-results/scheduled-export/edit/%s', $nid, $existing_export->id);
-      $message = 'Webform scheduled export has been updated.';
-    }
-    else {
-      $url = sprintf('node/%s/webform-results/scheduled-export/add', $nid);
-      $message = 'Webform scheduled export has been created.';
-    }
-    $this->drupalPost($url, array(
-        'email' => is_array($emails) ? implode("\n", $emails) : $emails,
-        'frequency' => $frequency,
-        'send_date[date]' => $date,
-        'send_date[time]' => $time,
-      ) + $extra, 'Save export configuration');
-    $this->assertText($message);
-  }
-
-  /**
-   * Assert the expected emails received the export.
-   *
-   * @param array $expected_emails
-   *   The emails that we expected.
-   * @param string $key
-   *   The email key to filter on.
-   *
-   * @return array
-   *   The scheduled export emails.
-   */
-  protected function assertEmails(array $expected_emails, $key = 'email_attachment') {
-    $emails = $this->drupalGetMails(array('key' => $key));
-    $this->assertEqual(count($expected_emails), count($emails), t('Expected !expected emails and sent !sent emails', array(
-      '!expected' => count($expected_emails),
-      '!sent' => count($emails),
-    )));
-
-    foreach ($expected_emails as $delta => $mail) {
-      $this->assertEqual($emails[$delta]['to'], $mail);
-    }
-
-    // Reset the email collector.
-    variable_set('drupal_test_email_collector', array());
-
-    return $emails;
-  }
-
-  /**
-   * Helper to create a webform node with a submission.
-   */
-  protected function createWebformAndSubmission() {
-    $node = $this->drupalCreateNode(array('type' => 'webform'));
-    $this->drupalPost(sprintf('node/%d/webform', $node->nid), array(
-      'add[name]' => 'test_component',
-    ), 'Add');
-    $this->drupalPost(NULL, array(), 'Save component');
-    $this->drupalPost(sprintf('node/%d', $node->nid), array(
-      'submitted[test_component]' => 'my custom value',
-    ), 'Submit');
-    return $node;
-  }
-
 }
diff --git a/tests/webform_scheduled_export_base.test b/tests/webform_scheduled_export_base.test
new file mode 100644
index 0000000..d7bc168
--- /dev/null
+++ b/tests/webform_scheduled_export_base.test
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * Base class for testing the scheduled export.
+ */
+abstract class WebformScheduledExportBase extends DrupalWebTestCase {
+
+  /**
+   * The profile to install as a basis for testing.
+   *
+   * @var string
+   */
+  protected $profile = 'testing';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp(array('webform_scheduled_export', 'node', 'webform'));
+    $account = $this->drupalCreateUser(array(
+      'bypass node access',
+      'edit webform components',
+      'access all webform results',
+    ));
+    $this->drupalLogin($account);
+  }
+
+  /**
+   * Configure our export.
+   */
+  protected function configureExport($nid, $emails, $frequency, $date, $time, $extra = array(), $edit_existing = TRUE) {
+    if ($edit_existing && $existing_export = WebformScheduledExportConfig::loadByWebformNid($nid)) {
+      $existing_export = array_shift($existing_export);
+      $url = sprintf('node/%s/webform-results/scheduled-export/edit/%s', $nid, $existing_export->id);
+      $message = 'Webform scheduled export has been updated.';
+    }
+    else {
+      $url = sprintf('node/%s/webform-results/scheduled-export/add', $nid);
+      $message = 'Webform scheduled export has been created.';
+    }
+    $this->drupalPost($url, array(
+        'email' => is_array($emails) ? implode("\n", $emails) : $emails,
+        'frequency' => $frequency,
+        'send_date[date]' => $date,
+        'send_date[time]' => $time,
+      ) + $extra, 'Save export configuration');
+    $this->assertText($message);
+  }
+
+  /**
+   * Assert the expected emails received the export.
+   *
+   * @param array $expected_emails
+   *   The emails that we expected.
+   * @param string $key
+   *   The email key to filter on.
+   *
+   * @return array
+   *   The scheduled export emails.
+   */
+  protected function assertEmails(array $expected_emails, $key = 'email_attachment') {
+    $emails = $this->drupalGetMails(array('key' => $key));
+    $this->assertEqual(count($expected_emails), count($emails), t('Expected !expected emails and sent !sent emails', array(
+      '!expected' => count($expected_emails),
+      '!sent' => count($emails),
+    )));
+
+    foreach ($expected_emails as $delta => $mail) {
+      $this->assertEqual($emails[$delta]['to'], $mail);
+    }
+
+    // Reset the email collector.
+    variable_set('drupal_test_email_collector', array());
+
+    return $emails;
+  }
+
+  /**
+   * Helper to create a webform node with a submission.
+   */
+  protected function createWebformAndSubmission() {
+    $node = $this->drupalCreateNode(array('type' => 'webform'));
+    $this->drupalPost(sprintf('node/%d/webform', $node->nid), array(
+      'add[name]' => 'test_component',
+    ), 'Add');
+    $this->drupalPost(NULL, array(), 'Save component');
+    $this->drupalPost(sprintf('node/%d', $node->nid), array(
+      'submitted[test_component]' => 'my custom value',
+    ), 'Submit');
+    return $node;
+  }
+
+}
diff --git a/webform_scheduled_export.admin.inc b/webform_scheduled_export.admin.inc
index 8b7b2d7..e132764 100644
--- a/webform_scheduled_export.admin.inc
+++ b/webform_scheduled_export.admin.inc
@@ -70,6 +70,7 @@ function webform_scheduled_export_form($form, &$form_state, $schedule_export_con
       'daily' => t('Daily'),
       'weekly' => t('Weekly'),
       'monthly' => t('Monthly'),
+      'calendar_month' => t('Calendar month'),
     ),
   );
 
@@ -147,6 +148,10 @@ function webform_scheduled_export_form_validate(&$form, &$form_state) {
   if (!$send_date) {
     form_set_error('send_date', t('Invalid date'));
   }
+
+  if ($form_state['values']['frequency'] === 'calendar_month' && $send_date->format('j') > 28) {
+    form_set_error('frequency', t('To export according to a calendar month, you must select the 28th or less for any given month.'));
+  }
 }
 
 /**
diff --git a/webform_scheduled_export.info b/webform_scheduled_export.info
index 9e88f54..1270fd9 100644
--- a/webform_scheduled_export.info
+++ b/webform_scheduled_export.info
@@ -11,3 +11,4 @@ dependencies[] = drupal:system (>= 7.40)
 files[] = includes/WebformScheduledExportConfig.php
 files[] = includes/WebformScheduledExportUIController.php
 files[] = tests/webform_scheduled_export.test
+files[] = tests/webform_calendar_month_scheduled_export.test
diff --git a/webform_scheduled_export.module b/webform_scheduled_export.module
index 8061151..5a31072 100644
--- a/webform_scheduled_export.module
+++ b/webform_scheduled_export.module
@@ -151,3 +151,21 @@ function _webform_scheduled_export_get_sids($node, $options) {
 function webform_scheduled_export_config_load($id) {
   return entity_load_single('webform_scheduled_export_config', $id);
 }
+
+/**
+ * Increment the given send date by a calendar month.
+ *
+ * @param int $send_date
+ *   A timestamp for when a submission was scheduled to be sent, which has now
+ *   completed successfully.
+ *
+ * @return int
+ *   A timestamp which represents the same date and time, for the next calendar
+ *   month.
+ */
+function webform_scheduled_export_increment_by_calendar_month($send_date) {
+  $time = new \DateTime(sprintf('@%d', $send_date));
+  $month_interval = new \DateInterval('P1M');
+  $time->add($month_interval);
+  return $time->getTimestamp();
+}
