Developer Reference

Sendpit Documentation

Complete technical reference for integrating email testing into your development workflow.

Sendpit Developer Documentation

1. Introduction

Sendpit is an email testing platform designed for development and QA workflows. It provides isolated SMTP mailboxes that capture outgoing emails, allowing teams to test email functionality without sending messages to real recipients.

Quick Start: If you just need to configure SMTP and send test emails, see the Integration Guide for language-specific examples (PHP, Node.js, Python, Ruby, Java, Go).

Who This Documentation Is For

  • Backend developers integrating email sending into applications
  • QA engineers validating email content and delivery
  • DevOps teams setting up CI/CD pipelines with email verification
  • Technical teams building automation around email testing

Common Use Cases

  • Development Testing: Capture emails from local or staging environments
  • CI/CD Integration: Verify transactional emails as part of automated test suites
  • QA Verification: Inspect email content, headers, and attachments before release
  • Webhook-Driven Automation: Trigger external workflows when emails are received

2. Getting Started

Creating an Organization

  1. Sign up at https://sendpit.com/register
  2. An organization is automatically created for your account
  3. You become the organization owner with full administrative access

Organizations are the top-level container for all resources. Team members, mailboxes, and billing are scoped to the organization.

Creating a Mailbox

  1. Navigate to Mailboxes in the dashboard
  2. Click Create Mailbox
  3. Enter a descriptive name (e.g., "Staging Environment", "CI Pipeline")
  4. Sendpit generates unique SMTP credentials automatically

Each mailbox receives:

  • Username: mb_ followed by 16 random characters
  • Password: 32 random characters
  • SMTP Host: smtp.sendpit.com
  • SMTP Port: 1025

Sending a Test Email

Configure your application's SMTP settings:

MAIL_MAILER=smtp
MAIL_HOST=smtp.sendpit.com
MAIL_PORT=1025
MAIL_USERNAME=mb_xxxxxxxxxxxxxxxx
MAIL_PASSWORD=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
MAIL_ENCRYPTION=null

Send an email using your application's normal email functionality. The message will appear in Sendpit within seconds.

Viewing Received Emails

  • Navigate to the mailbox in the dashboard
  • Emails are listed in reverse chronological order
  • Click any email to view full content, headers, and attachments

3. Authentication & API Access

Current API Limitations

Sendpit does not currently expose a public REST API for programmatic access to emails. Email retrieval is available through:

  • The web dashboard
  • Webhook notifications (Pro plans and above)

SMTP Authentication

SMTP access uses per-mailbox credentials. Each mailbox has independent credentials that:

  • Are generated automatically on mailbox creation
  • Can be regenerated at any time (invalidates previous credentials)
  • Are stored encrypted at rest

Rate Limits

Resource Limit
Emails per month Varies by plan (100 - 100,000+)
SMTP connections No hard limit; fair use applies
Webhook deliveries No rate limit per se; subject to plan quotas

4. Mailboxes

What a Mailbox Represents

A mailbox is an isolated SMTP endpoint that:

  • Accepts emails via SMTP on port 1025
  • Stores messages for later inspection
  • Can trigger webhooks on email receipt
  • Has its own access control (team members can be granted per-mailbox access)

Address Format

Emails sent to a mailbox can use any address format:

[email protected]          # Routed by SMTP credentials
[email protected]           # Captured based on SMTP auth
[email protected]   # Any sender/recipient works

The routing is determined by SMTP authentication, not the email address.

Per-Mailbox Behavior

Each mailbox maintains:

  • Independent email storage
  • Separate webhook configurations
  • Individual access control lists
  • Isolated credential sets

Lifecycle Considerations

  • Deletion: Deleting a mailbox permanently removes all associated emails
  • Credential Regeneration: Invalidates existing SMTP credentials immediately
  • Plan Downgrade: Excess mailboxes become read-only; new emails are rejected

5. Emails & Attachments

Email Fields Available

When viewing an email, the following fields are accessible:

Field Description
from Sender email address
to Recipient address(es)
subject Email subject line
received_at Timestamp when Sendpit received the email
content.text Plain text body
content.html HTML body
headers Full email headers
attachments List of attached files
size Total message size in bytes
read Whether the email has been viewed
starred User-applied star flag

Headers and Raw Email Access

Full email headers are preserved and viewable, including:

  • Message-ID
  • Date
  • Content-Type
  • Custom headers (e.g., X-Mailer, X-Priority)
  • DKIM and SPF headers (if present in original)

Raw email source is available for debugging MIME structure and encoding issues.

Attachment Handling

Attachments are:

  • Stored separately from email content
  • Accessible only to users with mailbox access
  • Available for download via the dashboard
  • Preserved with original filename and MIME type

Preview vs Download Behavior

  • Text/HTML emails: Rendered inline in the dashboard
  • Images: Previewed inline when safe
  • Other attachments: Download only (PDF, ZIP, etc.)

6. Webhooks

Overview

Webhooks provide real-time HTTP notifications when emails are received. Instead of polling, your application receives a POST request immediately after an email arrives.

Availability & Plans

Plan Webhook Access Webhooks per Mailbox
Free Not available 0
Basic Not available 0
Pro Available 5
Max Available 20
Enterprise Available Custom

Creating a Webhook

  1. Navigate to a mailbox in the dashboard
  2. Open SettingsWebhooks
  3. Click Add Webhook
  4. Enter your HTTPS endpoint URL
  5. Optionally add a description
  6. Save the webhook

Upon creation, Sendpit generates a unique signing secret. Store this secret securely—it's required for signature verification.

Requirements:

  • Endpoint must use HTTPS (HTTP is rejected)
  • Endpoint must respond within 10 seconds
  • Endpoint must return a 2xx status code

Webhook Payload

Sendpit sends a single event type: email.received

Example Payload:

{
  "event": "email.received",
  "timestamp": "2025-01-15T14:32:18+00:00",
  "webhook_id": 42,
  "mailbox": {
    "id": 15,
    "name": "Staging Environment"
  },
  "email": {
    "message_id": "<[email protected]>",
    "from": "[email protected]",
    "to": ["[email protected]"],
    "subject": "Order Confirmation #12345",
    "received_at": "2025-01-15T14:32:17+00:00"
  }
}

Important: The payload contains metadata only. Email body content and attachments are not included. Use the message_id or dashboard to retrieve full content.

Security & Signature Verification

Every webhook request includes cryptographic signatures for verification.

Headers Sent:

Header Description Format Example
X-Sendpit-Signature HMAC-SHA256 signature for payload verification sha256=<hex> sha256=a1b2c3...
X-Sendpit-Timestamp Unix timestamp when request was sent Integer (seconds) 1705329138
X-Sendpit-Webhook-Id Webhook configuration ID from your dashboard Integer 42
X-Sendpit-Event Event type that triggered the webhook String email.received
X-Sendpit-Delivery-Id Unique identifier for this delivery attempt UUID 550e8400-e29b-41d4-a716-446655440000
Content-Type Payload content type MIME type application/json
User-Agent Sendpit webhook client identifier String Sendpit-Webhook/1.0

Signature Verification (Recommended):

import hmac
import hashlib
import time

def verify_signature(secret: str, timestamp: str, payload: str, signature: str) -> bool:
    # Check timestamp is within 5 minutes (replay protection)
    current_time = int(time.time())
    request_time = int(timestamp)
    if abs(current_time - request_time) > 300:
        return False

    # Compute expected signature
    message = f"{timestamp}.{payload}"
    expected = "sha256=" + hmac.new(
        secret.encode(),
        message.encode(),
        hashlib.sha256
    ).hexdigest()

    # Constant-time comparison
    return hmac.compare_digest(expected, signature)
const crypto = require('crypto');

function verifySignature(secret, timestamp, payload, signature) {
  // Check timestamp is within 5 minutes
  const currentTime = Math.floor(Date.now() / 1000);
  const requestTime = parseInt(timestamp, 10);
  if (Math.abs(currentTime - requestTime) > 300) {
    return false;
  }

  // Compute expected signature
  const message = `${timestamp}.${payload}`;
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(message)
    .digest('hex');

  // Constant-time comparison
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}
function verifySignature(string $secret, string $timestamp, string $payload, string $signature): bool
{
    // Check timestamp is within 5 minutes
    $currentTime = time();
    $requestTime = (int) $timestamp;
    if (abs($currentTime - $requestTime) > 300) {
        return false;
    }

    // Compute expected signature
    $message = $timestamp . '.' . $payload;
    $expected = 'sha256=' . hash_hmac('sha256', $message, $secret);

    // Constant-time comparison
    return hash_equals($expected, $signature);
}
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "math"
    "strconv"
    "time"
)

func VerifySignature(secret, timestamp, payload, signature string) bool {
    // Check timestamp is within 5 minutes
    requestTime, err := strconv.ParseInt(timestamp, 10, 64)
    if err != nil {
        return false
    }
    currentTime := time.Now().Unix()
    if math.Abs(float64(currentTime-requestTime)) > 300 {
        return false
    }

    // Compute expected signature
    message := timestamp + "." + payload
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(message))
    expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))

    // Constant-time comparison
    return hmac.Equal([]byte(expected), []byte(signature))
}
// C# / .NET
using System;
using System.Security.Cryptography;
using System.Text;

public static class WebhookVerifier
{
    public static bool VerifySignature(string secret, string timestamp, string payload, string signature)
    {
        // Check timestamp is within 5 minutes
        if (!long.TryParse(timestamp, out long requestTime))
            return false;

        long currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
        if (Math.Abs(currentTime - requestTime) > 300)
            return false;

        // Compute expected signature
        string message = $"{timestamp}.{payload}";
        using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
        byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
        string expected = "sha256=" + BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();

        // Constant-time comparison
        return CryptographicOperations.FixedTimeEquals(
            Encoding.UTF8.GetBytes(expected),
            Encoding.UTF8.GetBytes(signature)
        );
    }
}

Delivery, Retries, and Failure Behavior

Retry Schedule:

If delivery fails, Sendpit retries with exponential backoff:

Attempt Delay After Failure
1 Immediate
2 1 minute
3 5 minutes
4 15 minutes
5 1 hour
6 2 hours (final)

Retriable Status Codes:

  • 408 Request Timeout
  • 429 Too Many Requests
  • 500 Internal Server Error
  • 502 Bad Gateway
  • 503 Service Unavailable
  • 504 Gateway Timeout

Non-Retriable Failures:

  • 4xx errors (except 408, 429) are not retried
  • Timeouts beyond 10 seconds
  • Connection failures

Auto-Disable Behavior:

After 10 consecutive failures, the webhook is automatically disabled. Re-enable it manually in the dashboard after fixing the endpoint issue.

Webhook Logs & Retention

Each webhook delivery attempt is logged with:

  • Timestamp
  • HTTP status code
  • Response time (ms)
  • Error message (if failed)
  • Delivery status (success, failed, retrying)

Retention: Logs are retained for 30 days and then permanently deleted.

No Manual Retry: There is no manual retry button. If a webhook fails permanently, you must retrieve the email data through the dashboard.

Common Pitfalls & Best Practices

Do:

  • Verify signatures on every request
  • Implement timestamp validation (reject requests > 5 minutes old)
  • Respond quickly (< 10 seconds)
  • Return 2xx even if async processing is needed
  • Store the webhook secret securely (environment variable, secrets manager)

Don't:

  • Rely on webhooks as the only notification mechanism
  • Assume webhooks will always succeed
  • Include sensitive data in your endpoint URL
  • Ignore the timestamp header
  • Process synchronously if it takes > 10 seconds

Idempotency & Duplicate Handling

Why Duplicates Occur:

Your endpoint may receive the same webhook multiple times due to:

  1. Retry logic: If your endpoint returns a 5xx error or times out, Sendpit retries delivery
  2. Network issues: A successful response may not reach Sendpit, triggering a retry
  3. Infrastructure failures: Temporary outages can cause duplicate dispatches

The Solution: Deduplicate by message_id

Every email has a unique message_id (the RFC 5322 Message-ID header). Use this as an idempotency key:

# Python with Redis
import redis

r = redis.Redis()

def handle_webhook(payload):
    message_id = payload['email']['message_id']

    # Atomic check-and-set (returns False if key exists)
    if not r.setnx(f"webhook:processed:{message_id}", "1"):
        return {"status": "duplicate"}, 200

    # Set TTL to auto-expire old keys (7 days)
    r.expire(f"webhook:processed:{message_id}", 604800)

    # Safe to process
    process_email(payload)
    return {"status": "ok"}, 200
// Node.js with in-memory Set (for single-instance apps)
const processedIds = new Set();

function handleWebhook(payload) {
  const messageId = payload.email.message_id;

  if (processedIds.has(messageId)) {
    return { status: 'duplicate' };
  }

  processedIds.add(messageId);

  // Prevent memory leak: cap size or use TTL cache
  if (processedIds.size > 10000) {
    const oldest = processedIds.values().next().value;
    processedIds.delete(oldest);
  }

  processEmail(payload);
  return { status: 'ok' };
}

Best Practices:

  • Always check for duplicates before processing
  • Use atomic operations (e.g., Redis SETNX, database unique constraints)
  • Set a reasonable TTL on idempotency keys (7-30 days)
  • Return 200 OK for duplicates—returning an error causes unnecessary retries

Webhook Troubleshooting

Signature Verification Failures

Symptom Likely Cause Solution
Signature never matches Wrong secret Re-copy the secret from the dashboard; ensure no trailing whitespace
Signature mismatch after deployment Secret rotation Update your environment variable with the new secret
Intermittent failures Payload modification Ensure you're using the raw request body, not a parsed/re-serialized version
Works locally, fails in production Encoding issues Verify UTF-8 encoding; check for proxy modifications

Debugging Signature Issues:

# Debug helper: print expected vs received
def debug_signature(secret, timestamp, payload, received_sig):
    message = f"{timestamp}.{payload}"
    expected = "sha256=" + hmac.new(
        secret.encode(), message.encode(), hashlib.sha256
    ).hexdigest()

    print(f"Timestamp: {timestamp}")
    print(f"Payload length: {len(payload)}")
    print(f"Expected: {expected}")
    print(f"Received: {received_sig}")
    print(f"Match: {hmac.compare_digest(expected, received_sig)}")

Timestamp Validation Errors

Symptom Likely Cause Solution
All requests rejected as expired Server clock drift Sync server time with NTP (ntpd or chrony)
Sporadic timestamp failures Edge cases near 5-min boundary Ensure tolerance check uses absolute value
Timestamp parse errors Non-integer value The header is always a Unix timestamp integer

Slow Endpoints Causing Retries

If your endpoint takes >10 seconds, Sendpit considers it a timeout and retries.

Solutions:

  1. Acknowledge immediately, process async:
from threading import Thread

@app.route('/webhook', methods=['POST'])
def webhook():
    payload = request.get_json()

    # Spawn background thread
    Thread(target=process_email, args=(payload,)).start()

    # Return immediately
    return '', 200
  1. Use a job queue:
from celery import Celery

@app.route('/webhook', methods=['POST'])
def webhook():
    payload = request.get_json()
    process_email_task.delay(payload)  # Queue for async processing
    return '', 200

Interpreting Webhook Delivery Logs

In your mailbox dashboard, each webhook shows a delivery log:

Status Meaning Action
success Endpoint returned 2xx None required
failed Non-retriable error (4xx except 408/429) Fix endpoint or URL
retrying Retriable error, will retry Check endpoint health
timeout No response within 10s Optimize endpoint or use async
disabled 10 consecutive failures Fix endpoint, then re-enable webhook

Recommended HTTP Responses

Scenario Status Code Body (optional)
Success 200 OK {"status": "ok"}
Duplicate (already processed) 200 OK {"status": "duplicate"}
Invalid signature 401 Unauthorized {"error": "invalid signature"}
Timestamp too old 401 Unauthorized {"error": "timestamp expired"}
Malformed payload 400 Bad Request {"error": "invalid json"}
Temporary failure 503 Service Unavailable {"error": "try again"}

Important: Always return 200 for successfully received webhooks, even if downstream processing fails asynchronously. Returning 4xx/5xx triggers retries.


7. Plan Enforcement & Limits

Feature Availability by Plan

Feature Free Basic Pro Max
Mailboxes 1 3 10 Unlimited
Team Members 2 5 15 Unlimited
Emails/Month 100 1,000 4,000 100,000
Email Retention 7 days 14 days 60 days 60 days
Webhooks No No Yes (5/mailbox) Yes (20/mailbox)

Behavior on Downgrade

When downgrading to a lower plan:

  • Excess mailboxes: Become read-only; new emails are rejected
  • Excess team members: Retain access but no new invitations allowed
  • Webhooks: Disabled if new plan doesn't support them; existing configs are preserved but inactive
  • Emails over retention: Purged according to new retention period

Limit Enforcement

Limits are checked at:

  • Mailbox creation time
  • User invitation time
  • Email ingestion time
  • Webhook creation time

Attempting to exceed limits returns an error with a clear message indicating the limit and current usage.


8. Error Handling & Status Codes

Common Errors

Scenario HTTP Status Message
Invalid SMTP credentials N/A (SMTP rejection) Authentication failed
Mailbox limit reached 403 Mailbox limit reached for your plan
User limit reached 403 User limit reached for your plan
Email quota exceeded 403 Monthly email limit exceeded
Webhook on unsupported plan 403 Webhooks require Pro plan or higher
Invalid webhook URL 422 Webhook URL must use HTTPS
Duplicate webhook URL 422 This URL is already configured

Developer Recommendations

  1. Check plan limits before operations: Query current usage before attempting to create resources
  2. Handle 403 gracefully: Display upgrade prompts or notify administrators
  3. Implement retry logic for transient failures: Network issues, rate limits
  4. Log error responses: Include the full error message for debugging

9. Security Model

Tenant Isolation

  • Organizations are fully isolated from each other
  • Mailbox data is scoped to the owning organization
  • Team members only see resources they have explicit access to
  • Database queries enforce organization-level filtering

Attachment Access Control

Attachments are protected by:

  • Authentication requirement (logged-in user)
  • Mailbox access verification (user must have mailbox access)
  • Signed URLs with expiration (where applicable)

Attachments are not publicly accessible.

Webhook Signing Guarantees

  • Secrets are generated using cryptographically secure random bytes (256-bit)
  • Secrets are encrypted at rest using AES-256
  • Signatures use HMAC-SHA256
  • Timestamp validation prevents replay attacks
  • Secrets can be regenerated at any time (invalidates old signatures)

10. CI / Automation Examples

GitHub Actions

name: Email Integration Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  email-tests:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Configure Sendpit SMTP
        run: |
          cat >> .env << EOF
          MAIL_MAILER=smtp
          MAIL_HOST=smtp.sendpit.com
          MAIL_PORT=1025
          MAIL_USERNAME=${{ secrets.SENDPIT_USERNAME }}
          MAIL_PASSWORD=${{ secrets.SENDPIT_PASSWORD }}
          MAIL_ENCRYPTION=null
          EOF

      - name: Run email tests
        run: npm test -- --grep "email"
        env:
          CI: true

GitLab CI

# .gitlab-ci.yml
stages:
  - test

email-integration:
  stage: test
  image: node:20-alpine

  variables:
    MAIL_MAILER: smtp
    MAIL_HOST: smtp.sendpit.com
    MAIL_PORT: "1025"
    MAIL_ENCRYPTION: "null"

  script:
    - npm ci
    - |
      cat > .env << EOF
      MAIL_MAILER=${MAIL_MAILER}
      MAIL_HOST=${MAIL_HOST}
      MAIL_PORT=${MAIL_PORT}
      MAIL_USERNAME=${SENDPIT_USERNAME}
      MAIL_PASSWORD=${SENDPIT_PASSWORD}
      MAIL_ENCRYPTION=${MAIL_ENCRYPTION}
      EOF
    - npm test

  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"

Store SENDPIT_USERNAME and SENDPIT_PASSWORD in GitLab CI/CD Variables (Settings → CI/CD → Variables).

Local Development with ngrok

Step 1: Install and start ngrok

# Install ngrok (macOS)
brew install ngrok

# Or download from https://ngrok.com/download

# Start tunnel to your local server
ngrok http 3000

Step 2: Copy the HTTPS URL

Session Status                online
Forwarding                    https://a1b2c3d4.ngrok-free.app -> http://localhost:3000

Step 3: Configure webhook in Sendpit

Use the ngrok HTTPS URL as your webhook endpoint:

https://a1b2c3d4.ngrok-free.app/webhooks/sendpit

Step 4: Test locally

// Express.js webhook handler for local testing
const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhooks/sendpit', (req, res) => {
  console.log('Webhook received:', JSON.stringify(req.body, null, 2));
  console.log('Headers:', req.headers);

  // Your signature verification here...

  res.status(200).json({ status: 'ok' });
});

app.listen(3000, () => console.log('Webhook server running on port 3000'));

Tips for local development:

  • ngrok URLs change on restart (free tier); update your webhook URL each time
  • Use ngrok http 3000 --log=stdout to see request logs in terminal
  • Consider ngrok's paid tier for stable URLs in team environments

Webhook-Driven Test Workflow

# test_webhook_handler.py
import time
from flask import Flask, request
import threading

app = Flask(__name__)
received_emails = []
email_event = threading.Event()

@app.route('/webhook', methods=['POST'])
def webhook():
    payload = request.get_json()
    received_emails.append(payload)
    email_event.set()
    return '', 200

def wait_for_email(timeout=30):
    """Block until an email is received or timeout."""
    email_event.clear()
    if email_event.wait(timeout):
        return received_emails[-1]
    raise TimeoutError("No email received within timeout")

# In your test
def test_welcome_email():
    # Trigger email send
    register_user("[email protected]")

    # Wait for webhook
    email = wait_for_email(timeout=10)

    assert email['email']['subject'] == "Welcome to Our Platform"
    assert email['email']['to'][0] == "[email protected]"

Idempotency in Automation

Store processed message IDs to handle retry scenarios:

import redis

r = redis.Redis()

def process_webhook(payload):
    message_id = payload['email']['message_id']

    # Check if already processed (atomic operation)
    if not r.setnx(f"processed:{message_id}", "1"):
        return  # Already handled

    # Set expiration (24 hours)
    r.expire(f"processed:{message_id}", 86400)

    # Process the email
    handle_email(payload)

11. FAQ

Can I retrieve email content via API?

Not currently. Email content is accessible through the dashboard or by building automation around webhooks plus dashboard access.

Why doesn't the webhook payload include the email body?

For security and performance reasons. Email bodies can be large and may contain sensitive data. The webhook notifies you of receipt; retrieve content through authenticated channels.

What happens if my webhook endpoint is down?

Sendpit retries delivery up to 5 times over approximately 3 hours. If all attempts fail, the webhook is logged as failed. After 10 consecutive failures across any deliveries, the webhook is auto-disabled.

Can I manually retry a failed webhook?

No. Failed webhooks cannot be manually retried. Design your system to handle missed webhooks by periodically checking the dashboard or implementing a polling fallback.

How do I test webhooks locally?

Use a tunneling service like ngrok:

ngrok http 3000
# Use the HTTPS URL as your webhook endpoint

Are emails permanently stored?

No. Emails are retained according to your plan's retention period (7-60 days), then automatically deleted.

Can I use custom domains?

SMTP credentials work regardless of the sender/recipient domain in emails. The routing is based on SMTP authentication, not email addresses.

What email size limits apply?

Maximum email size is 25 MB including attachments.


12. Versioning & Changelog Policy

Webhook Payload Versioning

The current webhook payload format is considered v1. Changes are handled as follows:

  • Additive changes (new fields): Deployed without notice. Your code should ignore unknown fields.
  • Breaking changes (removed fields, type changes): Announced 30 days in advance via email to organization owners.

Backward Compatibility

We commit to:

  • Not removing existing payload fields without notice
  • Not changing field types without notice
  • Maintaining signature algorithm compatibility

Changelog

Significant changes are documented in the application's release notes. Subscribe to product updates via your account settings.


13. Support & Feedback

Reporting Issues

For bugs, unexpected behavior, or security concerns:

  • Email: [email protected]
  • Include: Organization ID, mailbox name (not credentials), timestamp, and reproduction steps

Feature Requests

Submit feature requests through the in-app feedback form or email. We prioritize based on:

  • Impact on existing workflows
  • Alignment with product direction
  • Technical feasibility

Security Vulnerabilities

Report security issues to [email protected]. We follow responsible disclosure practices and will acknowledge receipt within 48 hours.


Last updated: December 2025

Last updated: January 2025

Questions? Contact [email protected]