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
- Sign up at
https://sendpit.com/register - An organization is automatically created for your account
- 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
- Navigate to Mailboxes in the dashboard
- Click Create Mailbox
- Enter a descriptive name (e.g., "Staging Environment", "CI Pipeline")
- 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-IDDateContent-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
- Navigate to a mailbox in the dashboard
- Open Settings → Webhooks
- Click Add Webhook
- Enter your HTTPS endpoint URL
- Optionally add a description
- 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:
408Request Timeout429Too Many Requests500Internal Server Error502Bad Gateway503Service Unavailable504Gateway Timeout
Non-Retriable Failures:
4xxerrors (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:
- Retry logic: If your endpoint returns a 5xx error or times out, Sendpit retries delivery
- Network issues: A successful response may not reach Sendpit, triggering a retry
- 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 OKfor 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:
- 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
- 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
- Check plan limits before operations: Query current usage before attempting to create resources
- Handle 403 gracefully: Display upgrade prompts or notify administrators
- Implement retry logic for transient failures: Network issues, rate limits
- 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=stdoutto 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