API Keys & Webhook Telemetry

Generate em_live_ API credentials and subscribe to cryptographically signed HMAC-SHA256 delivery webhooks.

EmitMail provides API keys and a webhook dispatcher that retries failed deliveries according to its queue configuration.


1. Generating API Keys

Navigate to Dashboard -> API Keys and click + Create API Key.

Assign a descriptive label (e.g., Production Backend API) and choose whether the key is scoped to a single verified domain or has multi-tenant workspace permissions.

Copy your secret API token immediately. Tokens use the standard format:

em_live_9f81a7b4892c48e89b33a5cf982e01b4

2. Webhook Event Subscriptions

Navigate to Dashboard -> Webhooks to register your server's callback URL. Supported events include:

Event IdentifierTrigger Condition
email.deliveredRemote MTA accepted message (250 OK status)
email.bouncedRemote MTA rejected message (550 HardFail or Mailbox Unavailable)
email.complaintRecipient marked email as spam (Feedback Loop ARF report)
email.suppressedOutbound dispatch blocked by recipient suppression list
email.unsubscribedUser clicked one-click List-Unsubscribe link
inbound.email.receivedIncoming email ingested and parsed by the /inbox engine

3. Verifying Webhook Signatures (HMAC-SHA256)

Webhook requests include these verification headers:

  • X-EmitMail-Signature: sha256=<hex_hmac_signature>
  • X-EmitMail-Event: Event name (e.g. email.delivered)
  • X-EmitMail-Delivery: Unique delivery attempt UUID

Node.js / Express Signature Verification Example

import crypto from 'node:crypto';
import express, { Request, Response } from 'express';

const app = express();
// Ensure you capture the raw body buffer for HMAC calculation
app.use(express.json({
  verify: (req: any, _res, buf) => {
    req.rawBody = buf;
  }
}));

app.post('/api/emitmail-webhook', (req: Request, res: Response) => {
  const signature = req.headers['x-emitmail-signature'];
  const webhookSecret = process.env.EMITMAIL_WEBHOOK_SECRET;
  const rawBody = (req as any).rawBody as Buffer | undefined;
  if (typeof signature !== 'string' || !webhookSecret || !rawBody) {
    return res.status(401).json({ error: 'Missing webhook verification data' });
  }

  const expectedSignature = `sha256=${crypto
    .createHmac('sha256', webhookSecret)
    .update(rawBody)
    .digest('hex')}`;

  const received = Buffer.from(signature);
  const expected = Buffer.from(expectedSignature);
  if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {
    return res.status(401).json({ error: 'Invalid webhook signature' });
  }

  const { event, data } = req.body;
  console.log(`Received verified event [${event}]:`, data);

  res.status(200).json({ received: true });
});

On this page