Venn

Notifications

Organizers can be told whenever someone responds to a poll, changes their answers or removes them. There are two ways:

Organizers set these up from the Notifications button on their poll. Email can also be turned on when creating a poll.

Turning on email

  1. Create a Resend account and verify your domain. Emails will come from an address on it.

  2. Create an API key with Sending access.

  3. Give the server two settings:

    Setting Example
    RESEND_API_KEY re_123… (secret)
    EMAIL_FROM Venn <notifications@example.com>

    On Cloudflare:

    npx wrangler secret put RESEND_API_KEY
    npx wrangler secret put EMAIL_FROM
    

    With Node, set them as environment variables.

The "Email me when people respond" field appears automatically once both settings are present.

How email stays safe

Webhooks

Paste any https:// URL. Venn sends a POST with a JSON body each time someone responds:

{
  "event": "response.created",
  "sentAt": "2026-11-02T15:04:05.000Z",
  "poll": {
    "id": "kz4d48daet",
    "title": "Design review",
    "url": "https://venn.example.com/e/kz4d48daet",
    "responses": 6,
    "finalStart": null
  },
  "response": { "name": "Ana" },
  "bestTime": {
    "start": "2026-11-04T20:00:00.000Z",
    "end": "2026-11-04T21:00:00.000Z",
    "available": 5,
    "ifNeeded": 1,
    "label": "Wed, Nov 4, 3:00 PM – 4:00 PM EST"
  }
}

event is one of response.created, response.updated, response.deleted, or test (from the Send test button).

Slack (hooks.slack.com) and Discord (discord.com/api/webhooks/…) URLs are recognized and receive a readable message instead, such as:

Ana responded to “Design review”. Best time so far: Wed, Nov 4, 3:00 PM – 4:00 PM EST (6 of 6 can make it). https://venn.example.com/e/kz4d48daet

Verifying requests

Each request is signed with the poll's signing secret (whsec_…, shown in the Notifications panel):

import { createHmac, timingSafeEqual } from 'node:crypto';

function isFromVenn(rawBody, headers, secret) {
  const timestamp = headers['x-venn-timestamp'];
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; // reject replays
  const expected = 'sha256=' + createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
  const given = headers['x-venn-signature'] || '';
  return given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}

Delivery rules