Agent skill

SMTP2GO Webhooks Skill

Receive and authenticate SMTP2GO email and SMS webhooks. Use when setting up an SMTP2GO webhook endpoint, checking the Authorization header (Bearer or Basic), debugging 401s, parsing JSON or form-encoded payloads, or handling events like delivered, bounce, open, click, spam and sms_failed.

Install this skill

npx skills add hookdeck/webhook-skills --skill smtp2go-webhooks


SMTP2GO (email and SMS delivery) sends an HTTP or HTTPS POST to your URL whenever an email or SMS event happens in your account. Payloads are not signed: there is no HMAC, signature header or timestamp. The only built-in credential is an optional Authorization header you configure on the webhook.

When to Use This Skill

  • How do I receive SMTP2GO webhooks?
  • How do I authenticate SMTP2GO webhooks? (Does SMTP2GO sign them? No: shared Authorization header only)
  • Why does my SMTP2GO webhook endpoint return 401?
  • How do I parse SMTP2GO's JSON or form-encoded webhook body?
  • How do I handle delivered, bounce, open, click, spam, unsubscribe or sms_failed events?

Verification (core)

Set an Authorization header on the SMTP2GO webhook and paste the full value, including the "Bearer " or "Basic " prefix.

The docs don't print the exact outgoing header (the API's auth_header_type is bearer or basic; auth_header_value is "either base64(user:pass) or a custom token"). Capture one delivery and set SMTP2GO_AUTHORIZATION to exactly what arrives in Authorization (expected Bearer <token> or Basic <base64>). Compare the whole header value; don't strip, parse or base64-decode the prefix.

const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifySmtp2goRequest(headers, expected = process.env.SMTP2GO_AUTHORIZATION) {
  if (!expected) return false;                // never accept when unconfigured
  const received = headers['authorization'];  // full value, e.g. "Bearer abc123"
  return Boolean(received) && safeEqual(received, expected);
}

Return 401 on a missing or mismatched header, 400 for a body you can't parse, 200 once handled. Read the body raw (express.raw({ type: '*/*' })) and parse it only after the check. Python and Next.js equivalents are in references/verification.md.

A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS, rotate the credential if it leaks, and dedupe on the webhook id.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Request Body

SMTP2GO sends a POST (not a GET) with a data output type of JSON or Form encoded, chosen in the webhook's settings. Parse application/json and application/x-www-form-urlencoded. Parameters are flat; dispatch on event.

Event Types

Kindevent values
Emailprocessed, delivered, open, click, bounce, spam, unsubscribe, resubscribe, reject
SMSsms_sending, sms_submitted, sms_delivered, sms_failed, sms_rejected

Common fields: event, time, sendtime, sender, rcpt, email_id, message-id, id (unique webhook id). Bounces add bounce (hard or soft), host and message. SMS events use destination_number, message_id, status_code, received_timestamp. See references/overview.md.

Retries

Failures retry for up to 48 hours (a maximum of 35 attempts), and the timeout is 10 seconds with no response headers. Respond fast with 200, and make handlers idempotent.

Environment Variables

SMTP2GO_AUTHORIZATION=Bearer your-token   # full Authorization header value, prefix included

Local Development

npx hookdeck-cli listen 3000 smtp2go --path /webhooks/smtp2go

No account required. Use the printed URL as the SMTP2GO webhook URL.

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 11, 2026

View on GitHub →