Keplars

Schedule Emails

Schedule emails for future delivery with timezone support

Schedule emails to be sent at a specific time in the future with full timezone support.

Paid Feature: Email scheduling is available on LAUNCH, CUSTOM, and ENTERPRISE plans. Free tier users can only send immediate emails.

Email Scheduling Features:

  • Schedule up to 365 days in advance
  • Timezone support (IANA timezone identifiers)
  • Works with both raw emails and templates
  • Cancel scheduled emails before they're sent

Quick Start

Schedule a Simple Email

curl -X POST https://api.keplars.com/api/v1/send-email/schedule \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scheduled_at": "2026-01-20_10:00:00",
    "timezone": "America/New_York",
    "email": {
      "from": "[email protected]",
      "from_name": "Newsletter Team",
      "to": ["[email protected]"],
      "subject": "Scheduled Newsletter",
      "html": "<h1>Your Weekly Update</h1><p>Here is this week newsletter...</p>"
    }
  }'

Schedule with Template

curl -X POST https://api.keplars.com/api/v1/send-email/schedule \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scheduled_at": "2026-01-25_09:00:00",
    "timezone": "UTC",
    "email": {
      "from": "[email protected]",
      "from_name": "Customer Success Team",
      "to": ["[email protected]"],
      "template_id": "019a83de-2fc2-7b5f-bfbc-c951fe1200f7",
      "params": {
        "customerName": "John Doe",
        "eventDate": "February 1st, 2026"
      }
    }
  }'

Date Format Examples

{
  "scheduled_at": "2026-01-20T15:30:00",
  "timezone": "Asia/Kolkata",
  "email": { ... }
}
{
  "scheduled_at": "2026-01-20_15:30:00",
  "timezone": "Asia/Kolkata",
  "email": { ... }
}

Both formats interpret the time in the specified timezone. In the example above, the email will be sent at 3:30 PM India time.

Schedule Request Format

Required Fields

  • scheduled_at (string): Date and time when to send the email. Supports two formats:
    • ISO 8601: 2026-01-20T10:00:00 (standard format)
    • Simplified: 2026-01-20_10:00:00 (easier to read/write)
    • Time is interpreted in the specified timezone (or UTC if not provided)
  • email (object): Email configuration (same format as Send Emails)

Optional Fields

  • timezone (string): IANA timezone identifier (default: UTC)
    • Examples: America/New_York, Europe/London, Asia/Tokyo, Asia/Kolkata, UTC
    • The scheduled_at time will be interpreted in this timezone

Email Object

The email object supports all the same fields as the regular send email endpoints:

Raw Email:

{
  "from": "[email protected]",
  "from_name": "Your Company",
  "to": ["[email protected]"],
  "cc": ["[email protected]"],
  "bcc": ["[email protected]"],
  "subject": "Scheduled Email",
  "html": "<p>Your content here</p>"
}

Template-based Email:

{
  "from": "[email protected]",
  "from_name": "Support Team",
  "to": ["[email protected]"],
  "template_id": "template-uuid",
  "params": {
    "variable1": "value1",
    "variable2": "value2"
  }
}

Sender Information:

  • from is mandatory when using custom domains or SMTP credentials
  • from_name is mandatory for custom domain emails (sets the sender's display name)
  • For OAuth emails (Gmail/Microsoft), from defaults to the connected email account
  • from_name is optional for OAuth emails but recommended for better recipient experience

Scheduling Constraints:

  • Minimum: 1 minute in the future
  • Maximum: 365 days in the future
  • Cannot schedule emails in the past

Schedule Response

Success Response

{
  "id": "scheduled_msg_1737307200_abc123",
  "object": "scheduled_email",
  "status": "scheduled",
  "scheduled_at": "2026-01-20T10:00:00",
  "timezone": "America/New_York",
  "from": "[email protected]",
  "to": ["[email protected]"],
  "subject": "Scheduled Newsletter",
  "created_at": "2026-01-19T18:00:00Z",
  "metadata": {
    "estimated_delivery": "Jan 20, 2026 at 10:00 AM (America/New_York)",
    "recipients_count": 1
  }
}

Error Response

{
  "error": {
    "type": "validation_error",
    "message": "scheduled_at must be at least 1 minute in the future"
  }
}

Cancel Scheduled Email

Cancel a scheduled email before it's sent:

curl -X DELETE https://api.keplars.com/api/v1/send-email/scheduled/{email_id} \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Campaign postponed"
  }'

Cancel Response

{
  "success": true,
  "message": "Scheduled email cancelled successfully"
}

Important: You can only cancel emails with status scheduled. Once an email is sent, it cannot be cancelled.

List Scheduled Emails

Get all scheduled emails for your workspace:

curl -X GET https://api.keplars.com/api/v1/send-email/scheduled \
  -H "Authorization: Bearer YOUR_API_KEY"

Filter by Status

curl -X GET "https://api.keplars.com/api/v1/send-email/scheduled?status=scheduled" \
  -H "Authorization: Bearer YOUR_API_KEY"

Available status values:

  • scheduled - Waiting to be sent
  • sent - Successfully sent
  • cancelled - Cancelled before sending
  • failed - Failed to send

List Response

{
  "object": "list",
  "data": [
    {
      "id": "scheduled_msg_1737307200_abc123",
      "workspace_id": "workspace-uuid",
      "created_by_user_id": "user-uuid",
      "scheduled_at": "2026-01-20T10:00:00Z",
      "timezone": "America/New_York",
      "status": "scheduled",
      "created_at": "2026-01-19T18:00:00Z"
    }
  ],
  "count": 1
}

Code Examples

// Schedule email
async function scheduleEmail(scheduledAt, timezone, emailData) {
  const response = await fetch('https://api.keplars.com/api/v1/send-email/schedule', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      scheduled_at: scheduledAt,
      timezone: timezone,
      email: emailData
    })
  });

  return response.json();
}

// Schedule using simplified format (recommended for readability)
const result = await scheduleEmail(
  '2026-01-21_09:00:00',
  'America/New_York',
  {
    from: '[email protected]',
    from_name: 'Onboarding Team',
    to: ['[email protected]'],
    subject: 'Welcome to our platform!',
    html: '<h1>Welcome!</h1><p>We are excited to have you.</p>',
  }
);

console.log('Scheduled email ID:', result.id);

// Cancel scheduled email
async function cancelScheduledEmail(emailId, reason) {
  const response = await fetch(`https://api.keplars.com/api/v1/send-email/scheduled/${emailId}`, {
    method: 'DELETE',
    headers: {
      'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ reason })
  });

  return response.json();
}

// Cancel if needed
await cancelScheduledEmail(result.id, 'Campaign postponed');

// List all scheduled emails
async function listScheduledEmails(status) {
  const url = new URL('https://api.keplars.com/api/v1/send-email/scheduled');
  if (status) url.searchParams.append('status', status);

  const response = await fetch(url, {
    headers: {
      'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`
    }
  });

  return response.json();
}

const scheduledEmails = await listScheduledEmails('scheduled');
console.log(`You have ${scheduledEmails.count} scheduled emails`);
interface EmailData {
  to: string[];
  cc?: string[];
  bcc?: string[];
  subject?: string;
  html?: string;
  template_id?: string;
  params?: Record<string, any>;
}

interface ScheduleEmailResponse {
  id: string;
  object: string;
  status: string;
  scheduled_at: string;
  timezone: string;
  from: string;
  to: string[];
  subject: string;
  created_at: string;
  metadata: {
    estimated_delivery: string;
    recipients_count: number;
  };
}

// Schedule email
async function scheduleEmail(
  scheduledAt: string,
  timezone: string,
  emailData: EmailData
): Promise<ScheduleEmailResponse> {
  const response = await fetch('https://api.keplars.com/api/v1/send-email/schedule', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      scheduled_at: scheduledAt,
      timezone: timezone,
      email: emailData
    })
  });

  if (!response.ok) {
    throw new Error(`Failed to schedule email: ${response.statusText}`);
  }

  return response.json();
}

// Schedule using simplified format (recommended)
const result = await scheduleEmail(
  '2026-01-21_09:00:00',
  'America/New_York',
  {
    from: '[email protected]',
    from_name: 'Onboarding Team',
    to: ['[email protected]'],
    subject: 'Welcome to our platform!',
    html: '<h1>Welcome!</h1><p>We are excited to have you.</p>',
  }
);

console.log('Scheduled email ID:', result.id);

// Cancel scheduled email
async function cancelScheduledEmail(
  emailId: string,
  reason?: string
): Promise<{ success: boolean; message: string }> {
  const response = await fetch(`https://api.keplars.com/api/v1/send-email/scheduled/${emailId}`, {
    method: 'DELETE',
    headers: {
      'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ reason })
  });

  return response.json();
}

// List all scheduled emails
async function listScheduledEmails(status?: string) {
  const url = new URL('https://api.keplars.com/api/v1/send-email/scheduled');
  if (status) url.searchParams.append('status', status);

  const response = await fetch(url, {
    headers: {
      'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`
    }
  });

  return response.json();
}

const scheduledEmails = await listScheduledEmails('scheduled');
console.log(`You have ${scheduledEmails.count} scheduled emails`);
import requests
from datetime import datetime, timedelta
import pytz
from typing import Dict, List, Optional

API_KEY = "your_api_key_here"
BASE_URL = "https://api.keplars.com/api/v1/send-email"

def schedule_email(
    scheduled_at: str,
    timezone: str,
    email_data: Dict
) -> Dict:
    """Schedule an email for future delivery."""
    response = requests.post(
        f"{BASE_URL}/schedule",
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json"
        },
        json={
            "scheduled_at": scheduled_at,
            "timezone": timezone,
            "email": email_data
        }
    )
    response.raise_for_status()
    return response.json()

# Schedule using simplified format (recommended)
result = schedule_email(
    "2026-01-21_09:00:00",
    "America/New_York",
    {
        "from": "[email protected]",
        "from_name": "Daily Reports",
        "to": ["[email protected]"],
        "subject": "Your Daily Report",
        "html": "<h1>Daily Report</h1><p>Here is your report...</p>",
    }
)

print(f"Scheduled email ID: {result['id']}")

def cancel_scheduled_email(email_id: str, reason: Optional[str] = None) -> Dict:
    """Cancel a scheduled email."""
    response = requests.delete(
        f"{BASE_URL}/scheduled/{email_id}",
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json"
        },
        json={"reason": reason} if reason else {}
    )
    response.raise_for_status()
    return response.json()

def list_scheduled_emails(status: Optional[str] = None) -> Dict:
    """List all scheduled emails, optionally filtered by status."""
    params = {"status": status} if status else {}
    response = requests.get(
        f"{BASE_URL}/scheduled",
        headers={"Authorization": f"Bearer {API_KEY}"},
        params=params
    )
    response.raise_for_status()
    return response.json()

scheduled = list_scheduled_emails("scheduled")
print(f"You have {scheduled['count']} scheduled emails")
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "time"
)

const baseURL = "https://api.keplars.com/api/v1/send-email"

type EmailData struct {
    From       string                 `json:"from,omitempty"`
    FromName   string                 `json:"from_name,omitempty"`
    To         []string               `json:"to"`
    CC         []string               `json:"cc,omitempty"`
    BCC        []string               `json:"bcc,omitempty"`
    Subject    string                 `json:"subject,omitempty"`
    HTML       string                 `json:"html,omitempty"`
    TemplateID string                 `json:"template_id,omitempty"`
    Params     map[string]interface{} `json:"params,omitempty"`
}

type ScheduleRequest struct {
    ScheduledAt string    `json:"scheduled_at"`
    Timezone    string    `json:"timezone"`
    Email       EmailData `json:"email"`
}

type ScheduleResponse struct {
    ID          string    `json:"id"`
    Object      string    `json:"object"`
    Status      string    `json:"status"`
    ScheduledAt string    `json:"scheduled_at"`
    Timezone    string    `json:"timezone"`
    From        string    `json:"from"`
    To          []string  `json:"to"`
    Subject     string    `json:"subject"`
    CreatedAt   string    `json:"created_at"`
}

func scheduleEmail(scheduledAt time.Time, timezone string, emailData EmailData) (*ScheduleResponse, error) {
    return scheduleEmailByString(scheduledAt.Format("2006-01-02T15:04:05"), timezone, emailData)
}

func scheduleEmailByString(scheduledAt string, timezone string, emailData EmailData) (*ScheduleResponse, error) {
    apiKey := os.Getenv("KEPLERS_API_KEY")

    reqBody := ScheduleRequest{
        ScheduledAt: scheduledAt,
        Timezone:    timezone,
        Email:       emailData,
    }

    jsonData, err := json.Marshal(reqBody)
    if err != nil {
        return nil, err
    }

    req, err := http.NewRequest("POST", baseURL+"/schedule", bytes.NewBuffer(jsonData))
    if err != nil {
        return nil, err
    }

    req.Header.Set("Authorization", "Bearer "+apiKey)
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }

    var result ScheduleResponse
    if err := json.Unmarshal(body, &result); err != nil {
        return nil, err
    }

    return &result, nil
}

func cancelScheduledEmail(emailID string, reason string) error {
    apiKey := os.Getenv("KEPLERS_API_KEY")

    cancelReq := map[string]string{"reason": reason}
    jsonData, _ := json.Marshal(cancelReq)

    req, err := http.NewRequest("DELETE", baseURL+"/scheduled/"+emailID, bytes.NewBuffer(jsonData))
    if err != nil {
        return err
    }

    req.Header.Set("Authorization", "Bearer "+apiKey)
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    return nil
}

func main() {
    // Schedule using simplified format
    // Note: Pass the datetime string directly to scheduleEmail
    // Modify scheduleEmail to accept string instead of time.Time for easier usage

    result, err := scheduleEmailByString(
        "2026-01-21_09:00:00",
        "America/New_York",
        EmailData{
            From:     "[email protected]",
            FromName: "Reports Team",
            To:       []string{"[email protected]"},
            Subject:  "Your Daily Report",
            HTML:     "<h1>Daily Report</h1><p>Here is your report...</p>",
        },
    )

    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }

    fmt.Printf("Scheduled email ID: %s\n", result.ID)

    // Cancel if needed
    err = cancelScheduledEmail(result.ID, "Campaign postponed")
    if err != nil {
        fmt.Printf("Cancel error: %v\n", err)
    }
}
<?php

class KeplarsEmailScheduler {
    private $apiKey;
    private $baseURL = 'https://api.keplars.com/api/v1/send-email';

    public function __construct($apiKey) {
        $this->apiKey = $apiKey;
    }

    public function scheduleEmail($scheduledAt, $timezone, $emailData) {
        $data = [
            'scheduled_at' => $scheduledAt,
            'timezone' => $timezone,
            'email' => $emailData
        ];

        return $this->makeRequest('POST', '/schedule', $data);
    }

    public function cancelScheduledEmail($emailId, $reason = null) {
        $data = $reason ? ['reason' => $reason] : [];
        return $this->makeRequest('DELETE', "/scheduled/{$emailId}", $data);
    }

    public function listScheduledEmails($status = null) {
        $url = '/scheduled';
        if ($status) {
            $url .= '?status=' . urlencode($status);
        }
        return $this->makeRequest('GET', $url);
    }

    private function makeRequest($method, $endpoint, $data = null) {
        $ch = curl_init($this->baseURL . $endpoint);

        $headers = [
            'Authorization: Bearer ' . $this->apiKey,
            'Content-Type: application/json'
        ];

        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);

        if ($data && ($method === 'POST' || $method === 'DELETE')) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        }

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpCode >= 400) {
            throw new Exception("API request failed with status {$httpCode}: {$response}");
        }

        return json_decode($response, true);
    }
}

// Usage example
$apiKey = getenv('KEPLERS_API_KEY');
$scheduler = new KeplarsEmailScheduler($apiKey);

// Schedule using simplified format (recommended)
$result = $scheduler->scheduleEmail(
    '2026-01-21_09:00:00',
    'America/New_York',
    [
        'from' => '[email protected]',
        'from_name' => 'Reports Team',
        'to' => ['[email protected]'],
        'subject' => 'Your Daily Report',
        'html' => '<h1>Daily Report</h1><p>Here is your report...</p>'
    ]
);

echo "Scheduled email ID: {$result['id']}\n";

// Cancel if needed
$scheduler->cancelScheduledEmail($result['id'], 'Campaign postponed');

// List scheduled emails
$scheduled = $scheduler->listScheduledEmails('scheduled');
echo "You have {$scheduled['count']} scheduled emails\n";
?>

Common Use Cases

1. Weekly Newsletter

Schedule a newsletter to send every Monday at 9 AM:

// Using simplified format for clarity
await scheduleEmail(
  '2026-01-27_09:00:00',  // Next Monday
  'America/New_York',
  {
    from: '[email protected]',
    from_name: 'Weekly Newsletter',
    to: subscribers,
    template_id: 'weekly-newsletter-template',
    params: { week: '2026-01-20' },
  }
);

2. Birthday Wishes

Schedule birthday emails in advance:

// Schedule for specific date and time
await scheduleEmail(
  '2026-02-15_09:00:00',
  'UTC',
  {
    from: '[email protected]',
    from_name: 'Celebrations Team',
    to: ['[email protected]'],
    template_id: 'birthday-template',
    params: { name: 'John', age: 30 },
  }
);

3. Event Reminders

Send reminders before an event:

// Send reminder a day before the event
await scheduleEmail(
  '2026-02-28_14:00:00',  // 24 hours before event
  'Europe/London',
  {
    from: '[email protected]',
    from_name: 'Event Reminders',
    to: ['[email protected]'],
    subject: 'Event Tomorrow!',
    html: '<p>Reminder: Your event is tomorrow at 2 PM.</p>',
  }
);

Timezone Support

Email scheduling supports all IANA timezone identifiers. Common timezones:

RegionTimezone Identifier
US EasternAmerica/New_York
US PacificAmerica/Los_Angeles
UKEurope/London
Central EuropeEurope/Paris
IndiaAsia/Kolkata
JapanAsia/Tokyo
Australia (Sydney)Australia/Sydney
UTCUTC

Find the full list of IANA timezones at IANA Time Zone Database

Error Codes

Error TypeDescription
validation_errorInvalid request format or constraints violated
template_access_deniedTemplate not found or no access permission
schedule_errorFailed to schedule email (system error)
cancellation_errorFailed to cancel (not found or wrong status)

Best Practices

  1. Always use timezone parameter - Specify the timezone for clarity, even if it's UTC
  2. Schedule well in advance - Don't schedule emails too close to the current time (minimum 1 minute)
  3. Test with templates - Test your templates before scheduling bulk emails
  4. Monitor scheduled emails - Regularly check the list of scheduled emails
  5. Cancel when needed - Cancel campaigns that are no longer relevant

Next Steps

  • Send Emails - Learn about immediate email sending
  • Email Templates - Create reusable templates
  • Webhooks - Get notifications when scheduled emails are sent
  • Examples - See integration examples for your programming language

Schedule emails effortlessly with Keplars Mail Service. Perfect for newsletters, campaigns, reminders, and automated communications.

On this page