Repository navigation
Echo Messages And Checks
Everything a site sends becomes a message in Echo: an email, an SMS, a Slack alert, a webhook, an analytics event, a payment call. This page covers the channels and the 53 services Echo recognizes. It covers what a message records, how it's masked and how to list and inspect messages. It also lists every check Echo runs on them, with its level and fix; turning checks off and adding your own is in Checks and rules.
| Channel | What | Examples |
|---|---|---|
email |
Mail handed to the site's mailer, the SMTP sink or sendmail, or sent through an email API |
wp_mail(), Laravel mailables, Nodemailer, SendGrid, the Gmail API |
sms |
Text messages through an SMS API | Twilio, Vonage, Plivo, Sinch |
whatsapp |
WhatsApp messages | WhatsApp Cloud API, Twilio with a whatsapp: number |
chat |
Team chat alerts | Slack, Discord, Microsoft Teams, Telegram bots |
webhook |
Anything posted to an API: CRMs, automation hooks, WooCommerce webhooks and any unrecognized POST, PUT or PATCH
|
Zapier, HubSpot, IFTTT, a webhook to your own app |
analytics |
Server-side tracking events | GA4 Measurement Protocol, Meta Conversions API, Segment, Mixpanel, PostHog |
payment |
Payment API calls | Stripe, PayPal, Adyen, Braintree, Square, Authorize.net, Mollie, Razorpay |
push |
Push notifications | FCM, APNs, OneSignal, Expo |
http |
Any other outbound request Echo couldn't classify (a GET, a DELETE, a tunnel through the proxy) |
Reports list channels in this order.
A connector reports a raw HTTP request; Echo works out which service it's for and pulls out the recipient, the content and the event. Each recognizer has positive and negative fixture tests in the Echo repository. Anything not listed is still captured, as a generic webhook (with its host and path as the recipient, long or token-like path segments shown as …) or a plain request. rhythm echo providers lists them by channel:
rhythm echo providersANALYTICS (5)
GA4 ga4 google-analytics.com, analytics.google.com
Meta Conversions API meta-capi graph.facebook.com
Mixpanel mixpanel api.mixpanel.com, api-eu.mixpanel.com, api-in.mixpanel.com (YAML)
PostHog posthog i.posthog.com, app.posthog.com, eu.posthog.com (YAML)
Segment segment api.segment.io, events.eu1.segmentapis.com (YAML)
CHAT (4)
Discord discord discord.com, discordapp.com
Slack slack hooks.slack.com, slack.com
Teams teams webhook.office.com, outlook.office.com, outlook.office365.com, logic.azure.com, api.powerplatform.com
Telegram telegram api.telegram.org
EMAIL (11)
Amazon SES amazon-ses email.*.amazonaws.com, email-fips.*.amazonaws.com
Brevo brevo api.brevo.com, api.sendinblue.com
Gmail API gmail gmail.googleapis.com, www.googleapis.com
Mailgun mailgun api.mailgun.net, api.eu.mailgun.net
Mailjet mailjet api.mailjet.com (YAML)
Mandrill mandrill mandrillapp.com
Microsoft Graph microsoft-graph graph.microsoft.com, graph.microsoft.us, dod-graph.microsoft.us, microsoftgraph.chinacloudapi.cn
Postmark postmark api.postmarkapp.com
Resend resend api.resend.com
SendGrid sendgrid api.sendgrid.com
SparkPost sparkpost api.sparkpost.com, api.eu.sparkpost.com
PAYMENT (8)
Adyen adyen *-live.adyenpayments.com, *-live.adyen.com, *-test.adyen.com (live calls refused)
Authorize.net authorize-net api.authorize.net, api2.authorize.net, apitest.authorize.net (live calls refused)
Braintree braintree api.braintreegateway.com, payments.braintree-api.com, api.sandbox.braintreegateway.com, payments.sandbox.braintree-api.com (live calls refused)
Mollie mollie api.mollie.com (live calls refused)
PayPal paypal api-m.paypal.com, api.paypal.com, api-m.sandbox.paypal.com, api.sandbox.paypal.com (live calls refused)
Razorpay razorpay api.razorpay.com (live calls refused)
Square square connect.squareup.com, connect.squareupsandbox.com (live calls refused)
Stripe stripe api.stripe.com (live calls refused)
PUSH (4)
APNs apns api.push.apple.com, api.sandbox.push.apple.com, api.development.push.apple.com
Expo Push expo exp.host, api.expo.dev (YAML)
FCM fcm fcm.googleapis.com
OneSignal onesignal onesignal.com
SMS (11)
Amazon SNS amazon-sns sns.*.amazonaws.com
Bandwidth bandwidth messaging.bandwidth.com (YAML)
Bird bird api.bird.com
ClickSend clicksend rest.clicksend.com
Infobip infobip api.infobip.com (YAML)
MessageBird messagebird rest.messagebird.com
Plivo plivo api.plivo.com
Sinch sinch sms.api.sinch.com (YAML)
Telnyx telnyx api.telnyx.com
Twilio twilio api.twilio.com
Vonage vonage rest.nexmo.com, api.nexmo.com, messages-sandbox.nexmo.com
WEBHOOK (9)
ActiveCampaign activecampaign api-us1.com, activehosted.com
HubSpot hubspot api.hubapi.com, api.hubspot.com, api.hsforms.com
IFTTT ifttt maker.ifttt.com
Klaviyo klaviyo a.klaviyo.com
Mailchimp mailchimp api.mailchimp.com
Make make hook.*.make.com, hook.*.integromat.com
n8n n8n n8n.cloud, n8n.*, *-n8n.*, *:5678
WooCommerce woocommerce any host, by its headers
Zapier zapier hooks.zapier.com
WHATSAPP (1)
WhatsApp Cloud API whatsapp-cloud graph.facebook.com
53 services recognized. Anything else is captured as a generic webhook or request.
--json gives the same list as JSON (provider, name, channel, hosts, yaml, payment). (YAML) marks a service described in a YAML file with its fixtures rather than in Go; payment gateways are always in Go, since they must refuse live calls. What each one records:
| Service | Channel | Recognized by | Records |
|---|---|---|---|
| SendGrid | POST api.sendgrid.com/v3/mail/send |
from, to, cc, bcc, subject, text, HTML | |
| Mailgun |
api.mailgun.net and api.eu.mailgun.net /v3/<domain>/messages
|
the same | |
| Postmark |
api.postmarkapp.com/email…, batches too |
the same | |
| Resend |
POST api.resend.com/emails and /emails/batch
|
the same | |
| Amazon SES |
email.<region>.amazonaws.com (v1 and v2 APIs) |
the same | |
| Brevo (Sendinblue) |
api.brevo.com and api.sendinblue.com /v3/smtp/email
|
the same | |
| SparkPost |
POST api.sparkpost.com/api/v1/transmissions (and EU) |
the same | |
| Mandrill | mandrillapp.com/api/1.0/messages/send… |
the same | |
| Mailjet | POST api.mailjet.com/v3.1/send |
the same | |
| Gmail API |
users.messages.send on gmail.googleapis.com and www.googleapis.com
|
the raw message, decoded: from, to, cc, bcc, Reply-To, subject, text, HTML | |
| Microsoft Graph |
POST …/users/{id}/sendMail and …/me/sendMail on graph.microsoft.com and the government and China clouds |
the same, from the JSON message or a base64 MIME body | |
| Twilio | sms, whatsapp | POST api.twilio.com/…/Messages.json |
to, from, body; whatsapp: numbers become WhatsApp; content templates as the event |
| Vonage | sms |
rest.nexmo.com/sms/json and the Messages API at api.nexmo.com/v1/messages
|
to, from, text |
| MessageBird | sms | POST rest.messagebird.com/messages |
recipients, originator, body |
| Bird | sms | POST api.bird.com/workspaces/…/channels/…/messages |
recipient, text |
| Plivo | sms | POST api.plivo.com/…/Account/…/Message |
dst, src, text |
| Amazon SNS | sms |
sns.<region>.amazonaws.com Publish to a phone number |
phone number, message |
| Telnyx | sms | POST api.telnyx.com/v2/messages |
to, from, text |
| ClickSend | sms | rest.clicksend.com/v3/sms/send |
each message's to and body |
| Sinch | sms |
/xms/v1/{plan}/batches on *.sms.api.sinch.com
|
to, from, body |
| Infobip | sms |
/sms/2/text/advanced, /sms/1/text/single, /sms/3/messages on *.api.infobip.com
|
destinations, text |
| Bandwidth | sms |
/api/v2/users/{account}/messages on messaging.bandwidth.com
|
to, from, text |
| WhatsApp Cloud API | POST graph.facebook.com/…/messages |
to, text or template name | |
| Slack | chat |
hooks.slack.com incoming webhooks, slack.com/api/chat.postMessage
|
channel, text |
| Discord | chat | discord.com/api/webhooks/… |
content |
| Microsoft Teams | chat |
webhook.office.com, outlook.office.com, Power Automate workflows (logic.azure.com, api.powerplatform.com) |
card text |
| Telegram | chat | Bot API send* calls on api.telegram.org
|
chat_id, text or caption; the method as the event for anything but sendMessage
|
| GA4 | analytics | Measurement Protocol on google-analytics.com and analytics.google.com
|
event names, as the event |
| Meta Conversions API | analytics | POST graph.facebook.com/…/events |
event names |
| Segment | analytics |
/v1/track, identify, page, screen, group, alias, batch, import on api.segment.io and events.eu1.segmentapis.com
|
the event |
| Mixpanel | analytics |
/track and /engage on api, api-eu and api-in.mixpanel.com
|
the call (the base64 data= form isn't read) |
| PostHog | analytics |
/capture, /batch, /e, /i/v0/e on PostHog Cloud |
the event |
| FCM | push | POST fcm.googleapis.com/v1/projects/…/messages:send |
token or topic, title, body |
| APNs | push |
POST /3/device/{token} on api.push.apple.com and the sandbox hosts |
the device token, the alert's title and body, the push type when it isn't alert
|
| Expo Push | push |
/--/api/v2/push/send on exp.host, /v2/push/send on api.expo.dev
|
the push tokens, title, body |
| OneSignal | push (email or sms when the call targets those) |
POST onesignal.com notifications API |
recipients or segments, heading, content |
| Stripe | payment | api.stripe.com |
the call (payment_intents create), amount and currency, test or live mode |
| PayPal | payment |
api-m.paypal.com, api.paypal.com and the sandbox hosts |
the call, sandbox or live mode |
| Adyen, Braintree, Square, Authorize.net | payment | their live and sandbox hosts | the call (payments create; Authorize.net: the request type), live or sandbox mode |
| Mollie, Razorpay | payment |
api.mollie.com, api.razorpay.com
|
the call, live or test mode by the key |
| WooCommerce webhooks | webhook | any request with an X-WC-Webhook-Topic header |
the topic (order.created) as the event, the delivery URL |
| HubSpot | webhook |
api.hubapi.com, api.hubspot.com, api.hsforms.com
|
the call (contacts create) or form submit, plus the contact's email |
| Mailchimp (Marketing API) | webhook | api.mailchimp.com/3.0/… |
the list member's email |
| Klaviyo | webhook | a.klaviyo.com |
the metric or call, plus the profile's email |
| ActiveCampaign | webhook |
…api-us1.com and …activehosted.com /api/3/…
|
the contact |
| Zapier | webhook | hooks.zapier.com |
the hook, with its secret part masked |
| IFTTT | webhook | maker.ifttt.com/trigger/{event}/… |
the event, as ifttt <event>, with the key masked |
| Make | webhook |
hook.*.make.com, hook.*.integromat.com
|
the hook, with its secret part masked |
| n8n | webhook |
/webhook/ and /webhook-test/ paths on *.n8n.cloud, on a host with an n8n label (n8n.shop.com, shop-n8n.example.net), or on port 5678 |
the hook, with its secret part masked |
rhythm echo doctor reports how many services are recognized (53 providers recognized). A WooCommerce webhook sent to a Zapier, Make or n8n hook is recorded as that hook's service, with the topic (order.created) as the event.
Titles and message lines use each service's display name: Amazon SES email, MessageBird SMS, Teams message, Meta Conversions API event, Stripe call, Zapier webhook, HubSpot webhook. A WooCommerce webhook and an unrecognized webhook name the host they go to (WooCommerce webhook to shop.example.com, webhook to crm.shop.test). Anything else is request to <host>. A tunnel through the proxy is HTTPS connection to <host>.
rhythm echo show 23bb60c909ae{
"schema": "rhythm-echo/v1",
"kind": "messages",
…
"data": [
{
"id": "23bb60c909ae",
"channel": "payment",
"provider": "stripe",
"subject": "live mode",
"text": "2400 eur",
"event": "payment_intents create",
"method": "POST",
"url": "https://api.stripe.com/v1/payment_intents",
"headers": {
"authorization": "•••••"
},
"body": "amount=2400¤cy=eur",
"response": {
"status": 403,
"body": "{\"error\":{\"code\":\"rhythm_echo_sealed\",\"message\":\"Rhythm Echo refused a live-mode call. Use a Stripe test key (sk_test_…) on staging.\",\"type\":\"invalid_request_error\"}}"
},
"status": "blocked",
"error": "refused: a live-mode Stripe call; use test credentials on staging",
"source": {
"via": "my-connector"
},
"at": "2026-10-25T14:36:09.833523391Z"
}
]
}show prints the same JSON envelope as every --json (see CLI reference), with the message in data.
| Field | Meaning |
|---|---|
id |
A short ID for show. |
run, action
|
The run and action that caused it, from X-Echo-Run; see Run IDs. |
attribution, late_for
|
late with the action it belongs to, for mail that arrived after its action ended; ambiguous when several actions were open and the message named none. |
channel, provider
|
What it is and which service. |
from, to, cc, bcc, reply_to, subject
|
For email; to is the number for SMS, the channel for chat, the hook for webhooks. |
text, html
|
The content a reader sees. |
event |
An analytics event, a webhook topic, a template, a payment call. |
method, url, headers, body
|
The raw request (or the raw MIME message for email), masked and capped. |
attachments |
An email's files: name, type, size and SHA-256, never the content. See Attachments. |
response |
What the service, or Echo's fake, answered. |
status |
faked, blocked, sent or failed; see Sealing staging. |
error |
Why a send failed or was refused. |
issues |
What the checks found in this message. |
truncated |
true when a field was cut to the size cap. |
source |
How Echo saw it: via (wordpress, laravel, node, smtp, sendmail, proxy), the stack, the plugin or package that sent it when known, the hook (wp_mail, pre_http_request, fetch, nodemailer…), the site's host and, on an agency hub, the site's ID (site). |
at |
When it was captured, in UTC. |
Credentials are masked before a message is stored, shown or written to a report:
-
Headers whose name contains
token,secret,key,auth,signature,session,password,cookie,credential,jwtorassertion(any case) are masked whole:•••••. That coversAuthorization,X-Auth-Token,X-Goog-Api-Key,X-Shopify-Access-Token,Set-Cookie,X-WC-Webhook-Signature,DKIM-Signatureand the like.X-Request-IdandContent-Typeare kept. -
URLs (the request's, every link in a text or HTML, every recipient): the values of the query keys
key,k,code,otp,reset,token,sig,signature,hash,auth,ticket,magic,apikeyand any key ending insignature,credential,secret,_token,-token,_keyor-key(X-Amz-Signature,login_token,api_key). A Telegrambot<id>:<token>and the segment afterkey,token,secretoraccess_tokenin a path keep at most a few characters. On a sign-in, magic, verify or reset link, the token in the query or the path is masked too. -
Texts and subjects: known secret shapes keep a short prefix (
sk_live_•••••), a JSON Web Token keeps its first 4 characters and one-time codes named as such are masked whole (Use 482913 to sign in.is stored asUse ••••• to sign in.); zip and postal codes are kept. -
JSON bodies: values under keys containing
pass,pwd,secret,tokenorauth(notauthor), ending inkey(apiKey,license_key) or ending inotp,pinorcodewith a code-like value (login_code,verificationCode, notzip_codeorstatus_code). -
A Zapier, Make or n8n hook URL is its own credential, so its secret last part is masked in
url,to, Echo Inbox and the reports:https://hooks.zapier.com/hooks/catch/1234567/ab•••••/. The masked form is the same every run, socomparestill pairs the same hook. -
The connectors mask credential headers the same way before anything leaves the site. A Stripe key keeps only its mode prefix (
Bearer sk_test_•••••), which is all the live-mode check needs.
A mask keeps nothing of a value that changes on every send (a reset key, a login code), so compare doesn't report it as changed. Echo reads one-time codes and links before it masks them and keeps them in memory beside the message, for rhythm echo latest and GET /v1/messages/latest; they're never written to a run, a snapshot, a kept inbox or a report.
Content isn't masked: names, addresses and order details are stored as sent. Each field is capped at 256 KiB (a cut one ends in …(truncated) and the message gets "truncated": true), a capture at 8 MiB and the whole inbox at 256 MiB, oldest first. Treat captures, .echo/ and reports like a database dump; see Responsible use. Terminal, Markdown and JUnit output escape control characters and bidi overrides in captured text (\x1b, \u202e), so a subject can't rewrite your terminal or reorder a line.
The running catcher keeps the newest 10,000 messages in memory and forgets them when it stops, unless you give it a store. Snapshots and runs keep their own copies in .echo/.
rhythm echo watch # new messages as they arrive (Ctrl+C to stop)
rhythm echo watch --json # one JSON object per line, for a script
rhythm echo ls # everything captured so far
rhythm echo ls --since 10m # the last ten minutes (also 30d)
rhythm echo ls --channel sms # one channel
rhythm echo ls --to 1555 # recipients containing "1555", ignoring case
rhythm echo ls --run 2026-10-24T08-29-07-cd7ff3 # one run
rhythm echo show 23bb60c909ae # one message as JSON
rhythm echo ls --json # all of them as JSON
rhythm echo clear # delete them (takes the same filters)d3e2ccbf826f 14:26:22 email to ann@shop.test: Your sign-in code [faked]
23bb60c909ae 14:36:09 Stripe call to api.stripe.com: live mode [blocked]
502758beb1e5 14:36:09 Stripe call to api.stripe.com: test mode [faked]
7cf14f21d822 14:36:09 Adyen call to checkout-live.adyenpayments.com: live mode [blocked]
181afd6a549a 14:38:18 email to jamie@shop.test: Payment received for order #10234 [faked]
That's the plain form, piped or with --plain. On a terminal each line has an icon per channel and columns for the kind, the recipient, the content and the status, under 100 columns. With -v, an email's files follow it ( attaches invoice-1042.pdf (application/pdf, 84 KB)).
watch prints each message's problems under it and a line when a message's status changes later (Update: 15:04:05 email to ann@shop.test is now blocked (was faked)). watch --since 10m first shows the last ten minutes. watch --json writes one object per line: {"type":"message","message":{…},"issues":[…]} for a new message and {"type":"update","message":{…},"was":"faked"} for a status change; the Watching … Ctrl+C to stop. line goes to stderr.
$ rhythm echo clear
Deleted 5 messages
watch, ls and clear take --channel, --to and --run (and --since, for ls and watch); an unknown channel is a usage error, as is an argument: rhythm echo: ls takes no arguments; narrow it with --run, --to, --channel or --since (rhythm echo show <id> for one). show with an unknown ID exits 2: no message "x" in Echo; list them with: rhythm echo ls. Echo Inbox, at http://127.0.0.1:8025, shows the same messages with search, every message's HTML and its checks.
Echo checks every message it catches: snapshot, compare and run list the problems in what each action sent. The live log of up, watch, Echo Inbox and the API show them on every capture. Soundcheck's --echo reports them as page issues. Each problem has a stable title, so a comparison can tell a new problem from one the snapshot already had: a new error is BROKE, a new warning or notice NEW and one that went away FIXED.
Three actions on a WordPress site whose order mailer, SMS add-on and webhook went wrong:
[ERROR] Unfilled placeholder *|ORDER_ID|* in email - Order email
The template still contains the placeholder, so readers see it instead of their data.
fix: Check the tag's spelling against the plugin's list of tags. Make sure the data it needs exists when the message is sent.
[ERROR] PHP error printed in email - Order email
PHP Warning: Undefined array key "total" (plugins/shop-mailer/templates/order.php line 42)
fix: Fix the code the error points at. Turn display_errors off so errors never reach messages.
[WARNING] Garbled characters in email - Order email
found "é": text was encoded twice
fix: Make sure the message is built and sent as UTF-8 (the mailer's charset and the template file's encoding).
[WARNING] Email links to a development address (localhost) - Order email
fix: Build links from the site's configured URL, not a hard-coded or local one.
[WARNING] SMS needs more than 3 parts - Shipping SMS
4 parts (GSM-7); each part is charged
fix: Shorten the message, or avoid characters that force UCS-2 (emoji, curly quotes).
[WARNING] WooCommerce webhook is sent without a signature - Order webhooks
fix: Set a secret on the webhook (WooCommerce → Settings → Advanced → Webhooks) so receivers can verify it.
[NOTICE] Email is sent from another domain (othermailer.com) - Order email
the site is shop.example.com
Messages have errors - 2 errors, 4 warnings, 1 notice - 3 actions, 1 email, 1 SMS, 1 webhook
Without --fail-on, run and snapshot exit 1 on an error (as here) and compare on a BROKE change; --fail-on warning is stricter, --fail-on none never fails. See CLI reference. rhythm echo checks lists every check; checks: in .echo/config.yaml changes a check's level or turns it off; rules: adds your own. See Checks and rules.
These apply to email, SMS, WhatsApp and chat messages. The text checked is the plain-text part, or the HTML with its tags removed, plus the subject.
| Check | Level | Title | Fix |
|---|---|---|---|
content/placeholder |
error | Unfilled placeholder {first_name} in email |
Check the tag's spelling against the plugin's list of tags. Make sure the data it needs exists when the message is sent. |
content/php-error |
error | PHP error printed in email | Fix the code the error points at. Turn display_errors off so errors never reach messages. |
content/encoding |
warning | Garbled characters in email | Make sure the message is built and sent as UTF-8 (the mailer's charset and the template file's encoding). |
content/dev-link |
warning | Email links to a development address (localhost) | Build links from the site's configured URL, not a hard-coded or local one. |
content/secret |
warning | Webhook to … carries a credential in its body | Don't put API keys or passwords in payloads; send them in an Authorization header, or not at all. |
-
Placeholders found:
{{ name }},*|FNAME|*(Mailchimp),%%name%%,{first_name}(lowercase names in single braces) and[your-name]or[_field](Contact Form 7). -
PHP errors:
Fatal error,Parse error,Warning,NoticeandDeprecatedlines with a file and line number, in plain text or PHP's HTML format. The detail shortens the path to what followswp-content/. -
Development addresses:
localhost,127.*,10.*,192.168.*,0.0.0.0and hosts ending in.local,.test,.localhost,.ddev.site,.lndo.site,.dockeror.invalid. This check is skipped when the configuredsite:is itself a development address, since then every link would match. -
Credentials in a body (
content/secret) is reported for any message but email whose stored body had a credential masked: a key or password field ("api_key":"ab•••••",DB_PASSWORD=•••••), an Authorization-style token, a/key/path segment, a Telegram bot token, a private key or a key Echo knows by its shape (sk_live_•••••). A one-time code, a reset or magic-link token and a signed URL are masked too but don't count, soYour code is •••••in an SMS isn't a finding. It applies to webhooks and other API calls, not only the channels above, for exampleMeta Conversions API event carries a credential in its body.
| Check | Level | Title | Fix |
|---|---|---|---|
email/empty |
error | Email is empty | The template rendered nothing: check the notification's message setting and the variables it uses. |
email/no-subject |
warning | Email has no subject | Give the notification a subject; many filters treat mail without one as spam. |
email/clipped |
warning | Email is too big for Gmail and gets clipped | Trim inline CSS and markup, or move content behind links. (Gmail clips HTML over 102 KB.) |
email/no-text |
notice | Email has no plain-text part | Send a text alternative too; some clients and spam filters prefer it. |
email/from-domain |
notice | Email is sent from another domain (othermailer.com) | Send from an address on your own domain, or make sure its SPF, DKIM and DMARC cover this sender. |
The From-domain check compares the sender's registrable domain with the site's, from the public suffix list, so mail.yoursite.com counts as yoursite.com and orders@other.co.uk on shop.co.uk is another domain. It's skipped when site: is unset, an IP address or a development address.
| Check | Level | Title | Fix |
|---|---|---|---|
sms/number |
warning | Message sent to a number that isn't in international format | Store and send numbers in E.164 form, e.g. +4915112345678. |
sms/parts |
warning | SMS needs more than 3 parts | Shorten the message, or avoid characters that force UCS-2 (emoji, curly quotes). |
sms/parts |
notice | SMS needs more than one part | Keep SMS under 160 GSM-7 characters (70 with emoji or other Unicode). |
Parts are counted the way carriers charge them: one GSM-7 part holds 160 characters (153 each when split, with ^{}\[~]|€ counting twice), one UCS-2 part 70 (67 each when split). Any character outside GSM-7 makes the whole message UCS-2.
| Check | Level | Title | Fix |
|---|---|---|---|
webhook/json |
warning | Webhook to crm.shop.test sends invalid JSON | The payload says it's JSON but doesn't parse; check how it's built (json_encode errors, stray output). |
webhook/signature |
warning | WooCommerce webhook is sent without a signature | Set a secret on the webhook (WooCommerce → Settings → Advanced → Webhooks) so receivers can verify it. |
| Check | Level | Title |
|---|---|---|
compat/html |
notice | Email uses display: grid, which Outlook for Windows, Gmail and Outlook.com drop |
compat/spam |
notice | SpamAssassin scores the email 2.1 of 5.0 (with up --spamassassin) |
See Checks and rules.
| Check | Level | Title | Fix |
|---|---|---|---|
send/failed |
error | Email fails, Twilio SMS fails (HTTP 401) | Check the sender's settings (API key, SMTP login, endpoint URL) and the provider's error above. |
seal/real-recipient |
warning | Email really sent to an address outside the allowlist | Run sealed on staging (rhythm echo up), or add test recipients to allow_recipients. |
seal/real-recipient only applies to messages a connector really sent (observe mode or an allowed host; never mail caught by the SMTP sink or sendmail). It ignores test addresses: see The allowlist.
| Check | Level | Title | Fix |
|---|---|---|---|
expect/unmet |
error | Sent fewer emails than expected (detail: email: expected 2, got 1); Sent fewer "purchase" analytics events than expected (analytics purchase: expected 1, got 0) |
Check the settings of whatever sends them, or update expect in .echo/config.yaml if the change is intended. |
These come from an action's expect:, one per channel or event short of its count; see Actions and compare. Comparisons add their own changes (a message no longer sent, a changed subject, a payload that lost fields); those are listed in Reading a comparison. Deliverability checks on DNS and headers are in Deliverability.
← Echo Inbox · Home · Checks and rules →
rhythm · Useful ideas. Working systems. · Check only sites you own or have permission to test. Keep staging sealed (responsible use) · This wiki is published from wiki/ in the rhythm repository; suggest changes there with a pull request.
Get started
rhythm in depth
Tuner
- Overview
- Your first scan
- Reading results
- Choosing checks
- Requests and politeness
- Output and reports
- Batch scans
- Local scans
- Integrity and malware
- Changes and notifications
Tuner in depth
- Known vulnerabilities
- PHP advisories
- CI and code scanning
- GitHub Action
- Config file
- Watch and serve
- Writing checks
- Check packs
- Finding reference
- CLI reference
- Coming from Wordfence CLI or WPScan
- FAQ and troubleshooting
Soundcheck
- Overview
- Your first check
- Reading results
- Review and accept
- Discovery
- Page health
- Visual changes
- Interactions
- Forms and flows
- Cache
- Speed
- Accessibility and SEO
Soundcheck in depth
- CI
- CI baselines and PR comments
- Configuration
- Error rules and exec
- WordPress updates
- Fleet and client reports
- History
- Import from BackstopJS and Lost Pixel
- Firefox and WebKit
- CLI reference
- FAQ and troubleshooting
- Benchmark
Echo
- Overview
- Your first run
- Connectors
- Sealing staging
- Echo Inbox
- Messages and checks
- Checks and rules
- Attachments
- Actions and compare
- With Soundcheck
- Replay
- Deliverability
Echo in depth
- Docker and DDEV
- Agency hub and reports
- Schedule
- MCP server
- Conformance kit
- CI
- Configuration
- API
- CLI reference
- FAQ and troubleshooting
- Benchmark
Project