Integrations
Connect forms, sheets, no-code tools, payments, stores, auth providers and CRMs to Unitpost through the API, webhooks or SMTP.
Unitpost connects to the tools you already use through its REST API, webhooks and SMTP relay. Each section below is the smallest reliable way to wire one tool in: what fires in that tool, where the API key goes, and the exact call into Unitpost. The vendor details (trigger names, plan limits, quotas) come from each vendor's own docs, checked 2026-10-24. Ask Unit or your own agent (through MCP) for the same advice applied to your setup.
No native apps yet
No native Zapier, Make, n8n, Google Sheets/Forms/Docs, Shopify, HubSpot or Salesforce app yet — connect with a generic HTTP step (Webhooks by Zapier, Make HTTP, n8n HTTP Request) or a small script, using an API key. Unitpost doesn't read Google Docs, Sheets or Notion by itself: something on your side (Apps Script, a no-code step, a script) pushes the data in. For a one-time list, export CSV and use the contact import instead. Unitpost doesn't host signup forms; the form lives in your site or form tool and posts the contact in. No IMAP/POP mailboxes — replies arrive as email.received webhooks. Unitpost doesn't send a third party's own notifications (Stripe receipts, Shopify order emails, Calendly confirmations): if you move those to Unitpost, switch the vendor's version off so people don't get two.
Choosing a pattern
- Start from where the data/event begins and the smallest reliable hop: a script in the tool you already use (Apps Script for Google, Run a script in Airtable, a webhook handler in your backend) beats adding a new paid product.
- No-code: if you already pay for Zapier, Webhooks by Zapier is fine (it isn't on Zapier's Free plan). On a budget or self-hosting, n8n (HTTP Request node) or Make (HTTP module) do the same. Free Zapier/Make poll every ~15 minutes, so 'instant' needs a webhook trigger or a paid plan.
- Payments, stores, auth providers push signed webhooks: verify the signature on the RAW body, answer 2xx fast, and pass the source event id as idempotency_key — every one of them retries and may deliver twice or out of order.
- Let the other tool say what happened and let Unitpost decide what to send (an event into an automation, or a new contact into a welcome journey), instead of building the email logic in Zapier.
- Only people who opted in or are customers go into Unitpost. A list of scraped, bought, rented or 'found' leads is a no under the Acceptable Use Policy — design an opt-in form instead.
Every pattern
| Tool | When it fits |
|---|---|
| Google Forms → Unitpost (new response → contact → welcome) | A Google Form is the signup/lead form and each response should become a contact (and get a welcome). |
| Google Sheets → Unitpost (rows as contacts, row changes as triggers) | Contacts or orders live in a Sheet that people (or other tools) keep adding to. |
| Google Docs as the content of an email or newsletter | You write the newsletter in a Doc and want to send it with Unitpost. |
| Zapier → Unitpost (Webhooks by Zapier) | You already run your glue in Zapier on a paid plan. |
| Make (make.com) → Unitpost (HTTP module) | No-code glue on a budget, or you already use Make. |
| n8n → Unitpost (HTTP Request node, or MCP) | You self-host or want the cheapest no-code glue; also the easiest to point at Unitpost's MCP server. |
| Stripe → Unitpost (payments and subscriptions) | Emails driven by payments: welcome after checkout, failed-payment (dunning), trial ending, cancellation. |
| Shopify → Unitpost (orders, abandoned checkouts, customers) | A Shopify store wants order follow-ups, abandoned-checkout or post-purchase journeys from Unitpost. |
| Supabase → Unitpost (signups, table changes, auth emails) | Your app runs on Supabase: welcome on signup, events from table changes, or sending Supabase's auth emails through Unitpost. |
| Firebase Auth / Firestore → Cloud Functions → Unitpost | Your app uses Firebase: welcome on signup, emails from Firestore writes. |
| Clerk → Unitpost (user.created, and sending Clerk's emails yourself) | Auth by Clerk: add new users as contacts, or deliver Clerk's verification emails through Unitpost. |
| Notion → Unitpost (database rows as contacts or events) | A Notion database tracks subscribers, leads or approvals. |
| Airtable → Unitpost (automation 'Run a script') | Contacts or orders live in Airtable. |
| Typeform / Tally / Webflow forms → Unitpost | A hosted form tool collects signups. |
| HubSpot / Salesforce → Unitpost | The CRM is the system of record and Unitpost sends. |
| Calendly → Unitpost (bookings) | Follow-ups and nurture around booked meetings. |
| Unitpost → Slack (internal alerts) | The team wants a Slack message on bounces, complaints, failed sends, or new replies. |
| WordPress / WooCommerce → Unitpost | A WordPress site or Woo store. |
| Segment / RudderStack → Unitpost (track events) | Product events already flow through a CDP. |
| Scheduled sends from your database (GitHub Actions, Vercel Cron, Supabase Cron) | Something due by date in your data (trial ends tomorrow, weekly digest). |
Google Forms → Unitpost (new response → contact → welcome)
A Google Form is the signup/lead form and each response should become a contact (and get a welcome).
- Trigger in the tool: Apps Script installable form-submit trigger (onFormSubmit) on the Form, or on its response Sheet.
- How it connects: The trigger's handler reads the answers and calls UrlFetchApp.fetch(POST JSON) to Unitpost.
- Where the API key lives: Script Properties (PropertiesService.getScriptProperties()); the script asks once for the external-request permission.
- Into Unitpost: POST /contacts, then POST /email with the welcome template.
- How fast: Instant, on every submission.
- Limit: Apps Script quotas: 20,000 URL fetches/day and 90 min total trigger runtime/day on consumer accounts (100,000 / 6 h on Workspace); 6 min per execution.
- Limit: An installable trigger runs as the person who created it — if that account loses access, it stops.
- Watch out: A function named onFormSubmit does nothing until the trigger is installed.
- Watch out: Turn on the form's 'Collect email addresses' and read getRespondentEmail(); a free-text Email question can hold anything.
- Watch out: Ask for marketing consent in the form if they'll get newsletters, not just the welcome.
function onSubmit(e) {
// Installable trigger (once): ScriptApp.newTrigger('onSubmit').forForm(FormApp.getActiveForm()).onFormSubmit().create();
const answers = {};
e.response.getItemResponses().forEach((r) => (answers[r.getItem().getTitle()] = r.getResponse()));
const key = PropertiesService.getScriptProperties().getProperty('UNITPOST_API_KEY');
const res = UrlFetchApp.fetch('https://www.unitpost.com/api/v1/contacts', {
method: 'post', contentType: 'application/json', muteHttpExceptions: true,
headers: { Authorization: 'Bearer ' + key },
payload: JSON.stringify({ email: e.response.getRespondentEmail() || answers['Email'], first_name: answers['Name'] }),
});
if (res.getResponseCode() >= 300 && res.getResponseCode() !== 409) console.error(res.getContentText());
}Google Sheets → Unitpost (rows as contacts, row changes as triggers)
Contacts or orders live in a Sheet that people (or other tools) keep adding to.
- Trigger in the tool: Apps Script installable Edit/Change trigger for rows people type in; a time-driven trigger (every 1/5/10/15/30 min) sweeping unsynced rows for rows other tools write.
- How it connects: UrlFetchApp POST per new row; write a 'synced' timestamp (or the contact id) back so a row is sent once.
- Where the API key lives: Script Properties.
- Into Unitpost: POST /contacts for new rows; POST /email with a template when a row needs an email now.
- How fast: Instant for edits; the sweep interval for rows written by other tools.
- Limit: A SIMPLE onEdit can't call external APIs (no authorized services, 30 s cap) — it must be an installable trigger.
- Limit: Same Apps Script quotas as Google Forms (20,000 fetches/day consumer).
- Watch out: Rows written by a script, the Sheets API or Zapier never fire edit/change triggers — use the time-driven sweep for those.
- Watch out: Edit triggers fire per cell: wait for a 'ready' checkbox or all required columns before sending.
- Watch out: For a one-time list, export CSV and import it in Contacts instead of row-by-row calls.
- Watch out: Ask where the list came from first: a sheet of scraped or bought leads can't be emailed (Acceptable Use Policy).
function sync() { // time-driven trigger, e.g. every 15 minutes
const sh = SpreadsheetApp.getActive().getSheetByName('Contacts');
const rows = sh.getDataRange().getValues(); // email, first, last, synced
const key = PropertiesService.getScriptProperties().getProperty('UNITPOST_API_KEY');
rows.slice(1).forEach((r, i) => {
if (!r[0] || r[3]) return;
const res = UrlFetchApp.fetch('https://www.unitpost.com/api/v1/contacts', { method: 'post', contentType: 'application/json',
headers: { Authorization: 'Bearer ' + key }, muteHttpExceptions: true,
payload: JSON.stringify({ email: r[0], first_name: r[1], last_name: r[2] }) });
const code = res.getResponseCode();
if (code < 300 || code === 409) sh.getRange(i + 2, 4).setValue(new Date());
});
}Google Docs as the content of an email or newsletter
You write the newsletter in a Doc and want to send it with Unitpost.
- Trigger in the tool: Manual (a menu/button in a bound script) or time-driven; Docs has no 'document changed' trigger.
- How it connects: Simplest: paste the text into a Unitpost template (or give it to Unit to lay out as a designed email). Automated: Drive export (text/markdown, text/plain or text/html) from Apps Script, poured into a template variable.
- Where the API key lives: Drive scope for the export; the Unitpost key in Script Properties.
- Into Unitpost: Same: a template filled from the export, sent as a campaign after review.
- How fast: On demand.
- Limit: Drive export is capped at 10 MB.
- Watch out: Exported Docs HTML is web HTML (stylesheet classes, not inline email styles) and its images point at Google-hosted URLs — don't mail it raw; use the text and a Unitpost template, and review before a broadcast.
- Watch out: getBody() reads only the first tab of a multi-tab Doc.
Zapier → Unitpost (Webhooks by Zapier)
You already run your glue in Zapier on a paid plan.
- Trigger in the tool: Any Zapier trigger (form/sheet/CRM app, Zapier Forms, or Webhooks by Zapier Catch Hook).
- How it connects: Action 'Webhooks by Zapier' → POST (or Custom Request), Payload Type JSON, header Authorization: Bearer <key>.
- Where the API key lives: The key sits in the step's header — anyone who can edit the Zap can see it; use a key limited to what the Zap does.
- Into Unitpost: Webhooks by Zapier POST /contacts, or POST /email with a template.
- How fast: Instant for webhook-based triggers; the polling interval otherwise (Google Sheets/Forms on Zapier usually poll).
- Limit: Webhooks by Zapier is a premium app: Professional, Team or Enterprise — not on the Free plan (Free is also limited to two-step Zaps and 100 tasks/month).
- Limit: Every step run is a task; polling triggers check every 15 min (Free), 2 min (Professional), 1 min (Team/Enterprise).
- Limit: Zapier's own MCP offering and an MCP-client step exist; a plain Webhooks step to the REST API is the stabler path.
- Watch out: There is no Unitpost app in Zapier's directory yet: use Webhooks by Zapier rather than searching for one.
- Watch out: Polling triggers can re-deliver after edits: send idempotency_key on events.
- Watch out: Free-plan users can't turn on a Zap with a Webhooks step: suggest Make, n8n or Apps Script instead.
Make (make.com) → Unitpost (HTTP module)
No-code glue on a budget, or you already use Make.
- Trigger in the tool: Any Make trigger (polling on the scenario schedule) or Webhooks > Custom webhook (instant).
- How it connects: HTTP > Make a request: POST, JSON body, Authorization: Bearer <key> (or the module's Credentials field).
- Where the API key lives: Make's keychain/Credentials, not a plain header, where possible.
- Into Unitpost: HTTP POST /contacts, or POST /email with a template.
- How fast: Instant with a webhook trigger; the schedule otherwise.
- Limit: Billing is in credits (1 credit per non-AI module run); Free has 1,000 credits/month.
- Limit: Scheduled scenarios run at most every 15 min on Free, every minute on paid plans (check Make's pricing page for current limits).
- Limit: Webhooks accept about 300 requests per 10 s; a webhook not attached to a scenario for 5 days is deactivated.
- Watch out: Every module run costs a credit, including polls that return items; iterators multiply runs.
- Watch out: There's no Unitpost app in Make yet — use the HTTP module.
n8n → Unitpost (HTTP Request node, or MCP)
You self-host or want the cheapest no-code glue; also the easiest to point at Unitpost's MCP server.
- Trigger in the tool: Any trigger node, Schedule Trigger, or the Webhook node (production URL only once the workflow is active).
- How it connects: HTTP Request node, Send Body JSON, credential type Bearer Auth (or 'Import cURL' from the Unitpost docs). MCP Client nodes can call mcp.unitpost.com directly.
- Where the API key lives: n8n credentials (stored encrypted, not in the node).
- Into Unitpost: HTTP Request → POST /contacts or POST /email with a template.
- How fast: Instant with a Webhook trigger; the interval for Schedule/polling triggers.
- Limit: n8n Cloud bills per workflow execution (a whole run, any number of steps); self-hosted Community Edition has no execution cap.
- Watch out: Test and production webhook URLs differ — 'works in test, silent in production' is usually the test URL.
- Watch out: Verify Unitpost webhook signatures in a Code node before trusting them.
Stripe → Unitpost (payments and subscriptions)
Emails driven by payments: welcome after checkout, failed-payment (dunning), trial ending, cancellation.
- Trigger in the tool: Stripe webhook events: checkout.session.completed (+ checkout.session.async_payment_succeeded), invoice.paid, invoice.payment_failed, customer.subscription.created|updated|deleted, customer.subscription.trial_will_end (3 days before), invoice.upcoming.
- How it connects: Your HTTPS endpoint or serverless function verifies the Stripe-Signature on the RAW body (stripe.webhooks.constructEvent), answers 2xx fast, then calls Unitpost.
- Where the API key lives: Stripe endpoint signing secret (whsec_…, different in test and live) and the Unitpost key in env vars.
- Into Unitpost: POST /email with the matching template from the webhook handler, idempotency_key = event.id.
- How fast: Instant (push).
- Limit: No plan gate; up to 16 event destinations; live-mode retries for up to 3 days.
- Watch out: No ordering guarantee and duplicates happen: pass the Stripe event.id as idempotency_key.
- Watch out: A framework that parses JSON first breaks signature verification — keep the raw body.
- Watch out: Stripe can send its own receipts, failed-payment and trial-ending emails: switch those off in Stripe if Unitpost sends them.
Shopify → Unitpost (orders, abandoned checkouts, customers)
A Shopify store wants order follow-ups, abandoned-checkout or post-purchase journeys from Unitpost.
- Trigger in the tool: Webhook topics orders/create, orders/paid, orders/fulfilled, checkouts/create, checkouts/update, customers/create.
- How it connects: Three routes: a webhook created in Settings → Notifications → Webhooks (no app); a custom or public app subscribing via the Admin API; or Shopify Flow's 'Send HTTP request' action posting straight to Unitpost.
- Where the API key lives: Verify X-Shopify-Hmac-Sha256 (base64 HMAC-SHA256 of the raw body); Flow can reference secrets in headers.
- Into Unitpost: orders/paid → POST /email with the follow-up template; customers/create → POST /contacts. Abandoned-checkout timing needs automations (or your own delayed job).
- How fast: Instant (push).
- Limit: Flow's Send HTTP request is on Grow, Advanced and Plus — not Basic.
- Limit: Merchants can't create new legacy admin custom apps since January 1, 2026 (existing ones keep working).
- Limit: Endpoint must answer within ~5 s; failures retry 8 times over 4 hours, then the subscription can be removed.
- Limit: Apps need Shopify's protected-customer-data approval for order/customer fields.
- Watch out: An abandoned checkout isn't an event: send checkout started, then let a Unitpost automation wait (e.g. 1–24 h) for order.paid and only remind when it doesn't come.
- Watch out: Dedupe on X-Shopify-Webhook-Id (pass it as idempotency_key).
- Watch out: Shopify sends its own order and abandoned-checkout emails: turn those off before sending them from Unitpost.
- Watch out: Only add customers who accepted marketing as newsletter contacts.
const digest = crypto.createHmac('sha256', process.env.SHOPIFY_CLIENT_SECRET).update(rawBody).digest('base64');
const ok = crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(req.headers['x-shopify-hmac-sha256'] || ''));
if (!ok) return res.sendStatus(401);Supabase → Unitpost (signups, table changes, auth emails)
Your app runs on Supabase: welcome on signup, events from table changes, or sending Supabase's auth emails through Unitpost.
- Trigger in the tool: Database Webhooks on INSERT/UPDATE/DELETE (payload: type, table, record, old_record); Auth Hooks (Send Email Hook replaces the built-in auth emails).
- How it connects: Database Webhook → Edge Function (fetch to Unitpost), so the key never sits in a trigger. Auth emails: Send Email Hook → Edge Function → POST /email, or point Supabase's custom SMTP at Unitpost SMTP.
- Where the API key lives: supabase secrets set UNITPOST_API_KEY=… (read with Deno.env.get); hook requests are signed (Standard Webhooks).
- Into Unitpost: insert → Edge Function → POST /contacts then POST /email with the welcome template; auth emails via the hook or SMTP.
- How fast: Instant (after commit).
- Limit: Send Email Hook is on Free and Pro; hook timeouts: Postgres 2 s, HTTP 5 s.
- Limit: Supabase's built-in SMTP sends only a handful of emails per hour (team addresses only) — production auth email needs custom SMTP or the hook.
- Limit: pg_net calls time out after 2 s by default and aren't retried — a failed call is silent.
- Watch out: Because pg_net doesn't retry, use an outbox table + cron for anything that must not be missed, or check net._http_response.
- Watch out: With the Send Email Hook on, SMTP isn't used and you build the confirmation link from token_hash + redirect_to.
Deno.serve(async (req) => {
const { type, record } = await req.json();
if (type !== 'INSERT') return new Response('ignored');
const r = await fetch('https://www.unitpost.com/api/v1/contacts', { method: 'POST',
headers: { Authorization: `Bearer ${Deno.env.get('UNITPOST_API_KEY')}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ email: record.email, first_name: record.first_name }) });
return new Response(await r.text(), { status: r.ok || r.status === 409 ? 200 : 502 });
});Firebase Auth / Firestore → Cloud Functions → Unitpost
Your app uses Firebase: welcome on signup, emails from Firestore writes.
- Trigger in the tool: Auth user created (onUserCreated / auth.user().onCreate); Firestore onDocumentCreated|Updated|Written.
- How it connects: A Cloud Function calls Unitpost with fetch or the unitpost SDK.
- Where the API key lives: Secret Manager via Firebase params (defineSecret).
- Into Unitpost: user created → POST /contacts + POST /email with the welcome template.
- How fast: Near-instant, asynchronous.
- Limit: Deploying functions needs the Blaze (pay-as-you-go) plan.
- Watch out: Delivery is at-least-once and unordered: use the uid / doc id as idempotency_key.
- Watch out: Anonymous users have no email — skip them.
Clerk → Unitpost (user.created, and sending Clerk's emails yourself)
Auth by Clerk: add new users as contacts, or deliver Clerk's verification emails through Unitpost.
- Trigger in the tool: Clerk webhooks (via Svix): user.created / user.updated / user.deleted; with 'Delivered by Clerk' off on a template, an email event carries the message for you to send (check the exact event name in the Dashboard's Event Catalog).
- How it connects: Route handler → verifyWebhook(req) (@clerk/nextjs/webhooks) → Unitpost.
- Where the API key lives: CLERK_WEBHOOK_SIGNING_SECRET (whsec_…); Svix headers.
- Into Unitpost: user.created → POST /contacts + POST /email; verification emails → POST /email.
- How fast: Near-instant but asynchronous and not guaranteed — don't depend on it for sign-in-critical flows without monitoring.
- Limit: Opt-out of Clerk delivery is per template.
- Watch out: At-least-once, unordered: dedupe on svix-id.
- Watch out: If Unitpost sends Clerk's verification codes, an outage in that handler blocks sign-ins.
Notion → Unitpost (database rows as contacts or events)
A Notion database tracks subscribers, leads or approvals.
- Trigger in the tool: Notion API webhooks (page.created, page.properties_updated, data_source.content_updated…), or a database automation's 'Send webhook' action; polling a data source as a fallback.
- How it connects: API webhooks carry ids only — the receiver reads the row with the Notion API, then calls Unitpost. 'Send webhook' posts properties in Notion's own format, so a relay (n8n/Make/function) reshapes it.
- Where the API key lives: Notion integration token; webhook signatures (X-Notion-Signature).
- Into Unitpost: relay → POST /contacts, or POST /email.
- How fast: API webhooks within minutes (most within a minute).
- Limit: 'Send webhook' is on paid plans; API rate limit ~3 requests/second per integration.
- Watch out: Delivery can be out of order; on a 'Send webhook' failure the automation pauses until someone resumes it.
Airtable → Unitpost (automation 'Run a script')
Contacts or orders live in Airtable.
- Trigger in the tool: Automation trigger 'When record matches conditions' (e.g. a Ready checkbox) or 'When a record is created'.
- How it connects: Action 'Run a script' with fetch() to Unitpost; record fields come in through input.config().
- Where the API key lives: Airtable script secrets (input.secret('UNITPOST_API_KEY')).
- Into Unitpost: Run a script → POST /contacts or POST /email.
- How fast: Near-instant.
- Limit: Automation runs per month: Free 100, Team 25,000, Business 100,000; a script makes at most 50 fetch calls per run (check whether scripts are on your plan).
- Watch out: 'When a record is created' can fire before fields are filled — trigger on a Ready condition.
const { email, firstName } = input.config();
const res = await fetch('https://www.unitpost.com/api/v1/contacts', { method: 'POST',
headers: { Authorization: `Bearer ${input.secret('UNITPOST_API_KEY')}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ email, first_name: firstName }) });
if (!res.ok && res.status !== 409) throw new Error(`Unitpost ${res.status}: ${await res.text()}`);Typeform / Tally / Webflow forms → Unitpost
A hosted form tool collects signups.
- Trigger in the tool: The tool's native 'new response' webhook (Typeform form_response, Tally FORM_RESPONSE, Webflow form_submission).
- How it connects: The native webhook can't reshape its JSON into Unitpost's body, so a small relay (serverless function, n8n, Make, Zapier Catch Hook) maps fields and calls Unitpost.
- Where the API key lives: Each tool signs its webhook (Typeform-Signature, Tally-Signature, x-webflow-signature — Webflow only for webhooks created through its API).
- Into Unitpost: relay → POST /contacts + POST /email with the welcome template.
- How fast: Instant.
- Limit: Tally webhooks are free; check Typeform's and Webflow's current plan pages for webhook availability.
- Watch out: Answer 2xx fast (Tally times out at 10 s); a 404/410 from the relay disables a Typeform webhook.
- Watch out: Add a marketing-consent checkbox to the form before adding people to newsletters.
HubSpot / Salesforce → Unitpost
The CRM is the system of record and Unitpost sends.
- Trigger in the tool: HubSpot workflow action 'Send a webhook'; Salesforce record-triggered Flow with an HTTP Callout.
- How it connects: HubSpot can map properties into a custom JSON body (no relay needed); Salesforce uses a Named/External Credential for the Authorization header.
- Where the API key lives: HubSpot stored secret (format the value as 'Bearer <key>'); Salesforce Named Credential.
- Into Unitpost: Send a webhook → POST /contacts, or POST /email.
- How fast: Near-instant on enrollment; HubSpot retries for up to 3 days.
- Limit: HubSpot 'Send a webhook' needs Data Hub Professional or Enterprise; without it, use a no-code tool polling the CRM.
- Watch out: Decide which system owns marketing email and unsubscribes; push Unitpost topic.unsubscribed / email.bounced webhooks back to the CRM.
Calendly → Unitpost (bookings)
Follow-ups and nurture around booked meetings.
- Trigger in the tool: Webhook subscriptions invitee.created, invitee.canceled, routing_form_submission.created.
- How it connects: Create the subscription with the API (POST /webhook_subscriptions); a relay maps the invitee to Unitpost.
- Where the API key lives: Calendly-Webhook-Signature (HMAC over t + '.' + body).
- Into Unitpost: invitee.created → POST /contacts + POST /email.
- How fast: Instant.
- Limit: Webhooks need a paid Calendly plan (Standard/Professional or higher), not Free.
- Watch out: Calendly sends its own confirmations and reminders — use Unitpost for follow-up, not duplicates.
- Watch out: Key journeys on the newest booking: a reschedule can show up as a cancel plus a new booking.
Unitpost → Slack (internal alerts)
The team wants a Slack message on bounces, complaints, failed sends, or new replies.
- Trigger in the tool: A Unitpost webhook subscribed to email.bounced / email.complained / email.failed / email.received / campaign.paused.
- How it connects: A relay verifies the Unitpost signature, formats { text } and posts to a Slack Incoming Webhook URL (or n8n/Make/Zapier in between).
- Where the API key lives: The Slack webhook URL is itself a secret — env var only.
- Into Unitpost: Webhook → relay → Slack.
- How fast: Instant.
- Limit: Slack incoming webhooks allow about 1 message per second.
- Watch out: Pointing a Unitpost webhook straight at Slack won't render — the payload formats differ.
- Watch out: Batch spikes (a bounce wave) into a digest.
import { verifyWebhook } from 'unitpost';
const evt = verifyWebhook({ payload: rawBody, headers: req.headers, secret: process.env.UNITPOST_WEBHOOK_SECRET });
await fetch(process.env.SLACK_WEBHOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: `Unitpost ${evt.type}: ${evt.data?.to ?? ''}` }) });WordPress / WooCommerce → Unitpost
A WordPress site or Woo store.
- Trigger in the tool: WooCommerce webhooks order.created|updated, customer.created; site mail through wp_mail.
- How it connects: Woo POSTs to a relay that verifies X-WC-Webhook-Signature and calls Unitpost; site mail via an SMTP plugin pointed at Unitpost SMTP.
- Where the API key lives: Woo webhook secret; SMTP password as a wp-config.php constant.
- Into Unitpost: order.updated (paid) → POST /email; customer.created → POST /contacts.
- How fast: Background (wp-cron by default) — low-traffic sites can lag without a real cron.
- Limit: A Woo webhook is disabled after 5 consecutive failed deliveries.
- Watch out: order.created can fire before payment: use order.updated with status processing/completed.
- Watch out: Woo sends its own order emails — avoid duplicates.
Segment / RudderStack → Unitpost (track events)
Product events already flow through a CDP.
- Trigger in the tool: track (e.g. 'Order Completed') and identify calls.
- How it connects: A Segment Destination Function (onTrack / onIdentify) or the Webhooks (Actions) destination; RudderStack's Webhook destination plus a transformation.
- Where the API key lives: Function settings (settings.apiKey) / destination headers.
- Into Unitpost: onIdentify → POST /contacts; send email from your backend with POST /email.
- How fast: Near real time; Segment retries functions that throw RetryError for hours.
- Limit: Turn batching off: Unitpost takes one event / contact per call.
- Watch out: Map messageId to idempotency_key.
- Watch out: An event without an email (or a userId → email mapping) can't reach a contact.
Scheduled sends from your database (GitHub Actions, Vercel Cron, Supabase Cron)
Something due by date in your data (trial ends tomorrow, weekly digest).
- Trigger in the tool: A schedule: GitHub Actions on.schedule, vercel.json crons, Supabase Cron (pg_cron).
- How it connects: The job selects due rows, calls Unitpost per row, marks them done.
- Where the API key lives: Repository/env secrets; protect a Vercel cron route with CRON_SECRET.
- Into Unitpost: cron → POST /email with a template per due row; store the returned email id on the row.
- How fast: The schedule (best effort).
- Limit: GitHub schedules run at most every 5 minutes and can be delayed or dropped under load; Vercel Hobby crons run once a day (±59 min).
- Watch out: Crons can miss or double-run: process 'everything due since the last success' and send idempotency_key = row id + purpose.
- Watch out: For time relative to a person ('3 days after signup'), a Unitpost automation with a wait step usually replaces the cron.