The June observability update gave delivery attempts a shared outcome-code vocabulary across integrations. This follow-up makes those codes more precise in cases that were still too broad, so failed deliveries are easier to triage from the event detail page and API.
Several destination responses now land on more specific outcome codes. A throttled request reports rate_limited, a missing destination record reports destination_not_found, invalid mapped fields report validation_failed, and expired credentials report auth_failed.
That precision makes it easier to compare delivery behavior across a workspace. Support teams can answer why a customer-facing integration failed, and product teams can build dashboards or alerts around actionable outcomes instead of broad error categories.
Common destination responses that previously appeared as broad client errors now report more specific outcomes, so the event detail page points closer to the action needed: fix a field value, reconnect credentials, retry after throttling, or update a missing destination reference.
The result is less guesswork when reviewing failed attempts. The attempt history shows the delivery status, the outcome code, and the destination response details where available, so a rejected event can be triaged from the dashboard or API without reproducing the delivery first.
The outcome-code enum remains additive from the June observability release. Existing clients that already handle the published values do not need a schema change, but clients that group errors by destination should see more failed attempts land in the most specific available category.
The per-attempt observability launch in May gave every delivery attempt a structured outcome code. This update makes those codes considerably more precise: outcome codes now reflect the destination's actual response, seven new codes cover the most common rejection modes, and failed attempts keep the provider's own error detail for inspection.
The outcome vocabulary grows from nine to sixteen codes. The new additions distinguish rejection modes that previously landed in the generic client-error bucket:
invalid_request — the destination rejected the request as malformedvalidation_failed — the destination rejected one or more field valuesdestination_not_found — the target resource no longer exists at the destinationdestination_conflict — the request conflicted with current destination statedestination_gone — the destination reported it is permanently gonedestination_timeout — the destination reported a request timeoutpayload_too_large — the destination refused the payload sizeThese join the existing delivered, rate_limited, auth_failed, timeout, network_error, mapping_error, rejected_client_error, rejected_server_error, and internal_error codes. The attempt drill-down on the event detail page renders all sixteen.
Outcome codes previously reported the broad category of a failure, so many distinct rejections looked the same. They now reflect the destination's actual HTTP response — the real status code plus the provider's own error code. A missing HubSpot list shows as destination_not_found, a rejected field value as validation_failed, a throttled request as rate_limited, and so on.
Every integration maps its destination's responses to the same shared vocabulary, so a rate_limited from HubSpot means the same thing as a rate_limited from a custom webhook — one set of codes to build dashboards and alerting against, regardless of where the event was headed.
When a delivery fails, the attempt now records a structured response: the HTTP status, the provider's own error code (for example, the error category HubSpot returns), and the destination's response body. The response details view in the attempt drill-down shows exactly what the destination said, so debugging a rejected delivery no longer requires reproducing the call yourself. Response details remain permission-gated the same way as before.
When a product event lands in a destination, you should know exactly what happened — not just whether it ended up green or red. This release turns every event in Meshes into a full delivery story, attempt by attempt, with the controls to act on what you find.
Open any event in the dashboard and each routing rule now expands inline to show the full attempt history. You can see the order things happened in, how long each attempt took, what the destination said, and which attempt finally succeeded or what is still being retried. Failed attempts that captured a response from the destination expose that response on demand, so debugging a flaky webhook or a misconfigured CRM no longer means digging through your own logs to figure out what Meshes saw.
Attempts now carry a structured outcome — delivered, rate limited, auth failure, timeout, server error, and others — so you can tell apart a transient destination blip from a credential problem from a mapping mistake at a glance. The same outcome flows through the API, so dashboards, automations, and support tooling can react to specific failure modes instead of treating every red row the same. The full list of outcome codes is in the API reference.
Event and rule status now distinguishes work that is actively being retried from work that has stopped. Retrying means a previous attempt failed but Meshes will try again; canceled means delivery has been stopped on purpose and will not resume. Combined with the new countdown that shows when the next attempt will fire, you always know whether an event is making progress or sitting still.
Two new controls live directly on the event detail page:
Both actions require workspace edit permission. Per-rule cancel and retry-now are available in embedded Meshes experiences your customers see in your own product; event-scope cancel stays inside your team's own dashboard.
Pipedrive is now available as a Meshes destination. Connect a Pipedrive account with OAuth and route product events into Person records and labels without building a separate Pipedrive sync path in your app.
The Pipedrive integration focuses on Person updates driven by the events you already send through Meshes.
Sync Person creates or updates a Pipedrive Person by email.Update Person Field syncs the mapped Person data and sets one supported editable Person field from the connected account.Add Label to Person and Remove Label from Person sync the mapped Person data and keep Person labels aligned with lifecycle and product status changes.A trial.started event can create or update the Person in Pipedrive, set a lifecycle custom field, and add a Person label for the trial state. From there, Pipedrive automations can handle follow-up tasks, deal routing, or campaign enrollment based on those Person updates.
Create a Pipedrive connection, add a rule for the event you want to send, choose the Person action that fits the workflow, and map the fields Meshes should write. Use Send Test Event to verify the Person update before turning the rule on. See the Pipedrive docs for setup and mapping details.
Customer.io is now available as a Meshes destination. Use it to identify people, sync customer traits, and track behavioral events from the product events your app already emits to Meshes.
The Customer.io integration helps lifecycle, growth, and product teams keep Customer.io profiles and campaigns aligned with real product activity.
userId, email, or anonymousId.event_name_override.A user.signup event can identify the new profile in Customer.io, attach traits like email, plan, and first_name, and record the signup event so Customer.io campaigns and segments can react to it.
Create a Customer.io connection, add a rule for the event you want to send, map the identity fields, and use Send Test Event to verify the delivery. See the Customer.io docs for the setup flow and field mapping details.
Discord is now available as a connection type in Meshes. Add the Meshes bot to your Discord server, pick the destination channel in the rule builder, configure an embed title and message template, and Meshes handles delivery with retries.
Each rule targeting a Discord connection includes a live channel picker backed by the connected server. After the bot is authorized, Meshes loads the available text and announcement channels so you can route each event to the right destination without hardcoded channel IDs.
Discord rules use a required embed title plus a message template with {{field}} interpolation. When an event fires, Meshes renders the title and body from the event payload and posts a rich embed to the selected channel.
Example rule values:
Title: Payment failed
Template:
Customer: {{customer.email}}
Plan: {{plan}}
Retry at: {{retry_at}}
Templates support nested field access such as {{user.name}}, so Discord notifications can stay concise while still carrying the product context your team needs.
Discord is the latest supported integration, joining HubSpot, Salesforce, Intercom, Mailchimp, ActiveCampaign, Resend, Zoom, AWeber, MailerLite, Slack, and Webhooks.
SendGrid is now available as a connection type in Meshes. You can connect a SendGrid account with an API key, select a Dynamic Template in the rule builder, or send an inline HTML email directly from the event that should trigger the message.
This integration supports two actions: Send Email (Template) for SendGrid Dynamic Templates and Send Email for inline HTML delivery. That means welcome emails, trial onboarding prompts, billing confirmations, and other transactional sends can all run from the same product events you already route to the rest of your stack.
No custom SendGrid worker is required. Create the connection in Connections -> New Connection -> SendGrid, add a rule for the event you care about, map email plus any template_variables you need, and Meshes handles the delivery path.
ActiveCampaign connections now support a Sync Contact action alongside the existing list and tag actions.
Sync Contact creates or updates the contact record in ActiveCampaign using the fields mapped in your Meshes rule. Unlike the list and tag actions, it does not require selecting a target list or tag — the mapped fields are the entire operation. This is useful when the goal is keeping contact data current without triggering list-based or tag-based automation.
email, firstName, lastName, phone) and custom fields from the connected account.email is required for all ActiveCampaign contact actions, including Sync Contact.
Meshes webhooks are no longer config-only. Every webhook connection now supports authenticated, signed delivery with full control over how the request reaches your endpoint.
Attach credentials to every webhook request. Choose from:
Authorization: Basic header from a username and passwordAuthorization: Bearer headerX-API-KeyAll credentials are encrypted at rest and never returned in plaintext after they are saved.
Enable HMAC-SHA256 or HMAC-SHA512 so your endpoint can cryptographically verify that every request body came from Meshes and has not been tampered with. The signature is computed from the exact bytes sent and attached in a configurable header (default X-Meshes-Signature). Signing is independent of authentication — use both together for defense in depth.
Add up to five static headers to every request for routing keys, tenant identifiers, or any metadata your endpoint expects.
Choose Wrapped to send the full Meshes event envelope, or Raw to send only the payload object. In raw mode, event metadata is promoted to X-Meshes-* HTTP headers so your endpoint can still access the event ID, name, resource, and timestamps.
Select POST, PUT, or PATCH per connection. The default is POST.
Meshes embed sessions now support two new additive access shapes on top of the existing workspace session model.
resource + resource_id pairlaunch_page replaces raw launch paths with a small initial-page enumdashboard, rules, or eventsdashboardlaunch_page instead of raw route stringssession_type when minting a sessionresource and resource_id only for resource sessionsYou can now embed key Meshes workspace pages directly inside your own product with session-based access and an iframe-ready launch flow.
This release adds a new embedded workspace surface for teams that want to expose integrations to their own customers without sending them to a separate admin app. Your backend mints a short-lived session, your frontend mounts the returned launch_url, and the embedded experience handles refresh, navigation, and resizing through a small postMessage contract.
POST /api/v1/sessions and POST /api/v1/sessions/:sessionId/refreshevents.payload:read session scopelaunch_urlmeshes:readymeshes:auth with the current access_tokenSlack is now available as a connection type in Meshes. Connect your Slack workspace via OAuth, pick a destination channel, configure a message template, and Meshes handles delivery with retries.
Each rule targeting a Slack connection includes a message template with {{field}} interpolation. When an event fires, Meshes renders the template with the event payload and posts to the configured channel.
Example template:
🎉 New signup: {{email}}
Plan: {{plan}}
Source: {{source}}
Templates support Slack markdown formatting and nested field access via dot notation (e.g., {{user.name}}). Missing fields render as empty strings rather than broken template syntax.
Slack is the latest supported integration, joining HubSpot, Salesforce, Intercom, Mailchimp, ActiveCampaign, Resend, Zoom, AWeber, MailerLite, and Webhooks.
Rule actions now support a textarea field type for multiline input. This powers Slack's message template editor and is available for any integration that needs multiline configuration in rule setup.
Previously, rule action fields only supported single-line text and password inputs. The new textarea type renders a resizable monospace text area in the rule configuration form - useful for templates, message bodies, or any structured text input.
This is a platform-level change, not specific to Slack. Any integration can now define a kind: "field" action data entry with type: "textarea" and get multiline input in the rule form automatically.
Meshes is now live with paid plans. Send product events from your app and route them to HubSpot, Salesforce, Intercom, Mailchimp, ActiveCampaign, Resend, Zoom, AWeber, MailerLite, and custom webhooks — with retries, deduplication, and delivery guarantees built in.
Sign up at meshes.io and send your first event in minutes. The Developer tier is free — 100 events/month, 1 workspace, 3 connections. Card required to activate your account.
Paid plans start at $49/mo (Builder) with annual pricing available with 2 months free.
When a delivery fails because the payload was wrong, retrying the same event just fails the same way again. And until now, the only way to send a corrected version was to go back to your own application and emit it from scratch. This release adds Re-emit: create a new event from an existing one — with the stored payload or an edited one — directly from the event detail page or the API.
Retry now re-runs a delivery of the same event. Re-emit is different: it creates a brand-new event with its own event ID, its own rule matching, and its own delivery attempts. The original event's payload, rules, and attempt history stay exactly as they were, so the record of what happened is never rewritten.
Re-emit works on an event in any state — completed, failed, canceled, or still retrying. You do not have to wait for an event to exhaust its retries before sending a corrected version; if you can already see what went wrong, you can act on it immediately.
The re-emit dialog shows the stored source payload and lets you edit the JSON before submitting, or leave it unchanged to reuse the original. A payment.failed event that was rejected because cus_greyjoy shipped with a malformed email can go back out with the corrected value in seconds — no code change, no redeploy. Edited payloads are validated the same way as ordinary event ingestion before the new event is accepted.
The dialog includes an Also cancel remaining attempts option. When selected, any deliveries on the original event that have not yet finished are canceled after the new event is accepted — in-flight requests are allowed to complete, and deliveries that already reached a final state are untouched. If some cancellations do not go through, the dashboard tells you exactly how many, so nothing fails silently.
Both events link to each other on the event detail page: the source event shows Re-emitted as links to every event created from it, and each new event shows a Re-emitted from link back to its source. The same provenance is available in the API as re_emitted_from and re_emitted_as on the event, so support tooling can walk the chain too. A single event can be re-emitted up to 100 times.
events.payload:read scope. Resource sessions can re-emit only events within their resource scope, and the new event keeps that same scope.POST /api/v1/events/{event_id}/re-emit accepts an optional payload override and an optional cancel_remaining flag, and returns the newly created event along with a cancel summary when cancellation was requested.Re-emitted events are processed like any other event you emit — they go through normal rule matching and deduplication, and they count toward your plan's event volume.
Two small improvements make integration setup and delivery recovery easier to reason about: Salesforce setup now calls out the package requirement earlier, and manual retries keep the visible next-attempt countdown in place.
Salesforce admins can use the Meshes package install link before starting OAuth, and the Salesforce integration docs now explain the org-level package requirement during setup.
That extra setup context helps teams prepare the Salesforce org before connecting it to Meshes, with the package prerequisite visible before a connection is configured.
Using Retry now on a rule in a retry loop no longer resets the next-attempt countdown shown on the event detail page. If the manual attempt fails, the countdown keeps pointing at the same upcoming automatic retry.
That keeps the recovery flow predictable. A manual retry can confirm whether a fix worked immediately, while the visible countdown remains stable if the destination is still unavailable or still rejecting the delivery.
A new endpoint, GET /api/v1/events/{event_id}/rules/{rule_id}, returns one rule execution for an event — status, attempt count, outcome, and next-attempt timing — without fetching the whole event. It is useful for polling a specific delivery after a retry, and it is available to embedded workspace and resource sessions with the same scoping as the event detail page.
The new outcome codes are additive to the outcome_code enum — existing responses still parse. Consumers that switch exhaustively on outcome codes should add branches for the seven new values. Attempts written before this release keep the codes they were recorded with.
The event and rule status enums now include retrying and canceled in addition to the existing values. The change is additive — existing responses still parse — but consumers that do an exhaustive switch on status should add branches for the two new values. Attempts written before this release do not carry an outcome code, so SDK clients should treat that field as optional.