Skip to main content
A custom HTML message is an in-app message whose content is your own document: a survey from Survicate, a promo built by your design team, or a custom HTML message you already run in Braze. Sequenzy sends it from a sequence step like any other in-app message, and the Sequenzy SDKs show it in a sandboxed web view over your app. Inside the document, a small JavaScript bridge lets it close itself, open links and deep links, log events on the contact and save attributes, so survey answers flow back into Sequenzy. The bridge also answers to Braze’s brazeBridge and appboyBridge names, so most Braze custom HTML runs unchanged.

Create one

  1. Add a Show In-App step to a sequence and pick HTML as the format.
  2. Paste your document, or click Upload .html. It can be up to 100 KB; load images, fonts and scripts from https URLs instead of inlining large files.
  3. Give it a Title. Screen readers announce it, and it names the message in the contact’s history. It is not drawn on screen.
  4. Leave Close button on unless your HTML always shows its own close control (sequenzy.close(), brazeBridge.closeMessage() or an element with data-sequenzy-close). Sequenzy turns it off for you when you upload or paste a document that has one.
The preview runs your document exactly as the SDKs will, with a sample contact filled in. Click through it: everything it asks the app to do (close, open a link, log an event, save an attribute) is listed under the preview and nothing is sent. Merge tags work anywhere in the document, for example <p>Hi {{firstName|there}}</p>. Two extra tags exist for HTML messages: {{contactId}} (the Sequenzy contact ID) and {{externalId}} (your own user ID). Each value is escaped for where it lands: as HTML in text and attributes, and as JavaScript string text inside <script> and on... attributes, so put tags in scripts inside quotes: var name = "{{firstName}}";. A contact’s own data, such as a name typed into a form, can’t change your document’s markup or code. You can also create HTML steps through the API (format: "html" with html on an in_app step), the CLI (sequenzy in-app html set) and MCP (insert_sequence_step or update_sequence_node). See CLI and MCP.

Which apps show them

Apps on older SDK versions, and apps that fetch the feed themselves, never receive HTML messages unless they ask for them with formats: ["modal", "banner", "fullscreen", "html"]. The message waits in the feed until a supported app shows it or it expires, so an older app version never shows a blank message in its place. A message acts only for the contact it was fetched for. When your app calls reset or identifies someone else, every SDK removes the previous contact’s message without reporting it, and ignores any bridge call it still sends.

The JavaScript bridge

Sequenzy adds the bridge to the top of your document before any of your own scripts run. Call it from your scripts or from onclick attributes. Links (<a href>) open through the app as clicks, so deep links work and the click is counted. Add data-sequenzy-ignore to a link to keep it inside the message. Any element with a data-sequenzy-close attribute closes the message when clicked, with no script needed, so it also works on websites whose Content Security Policy blocks onclick. The Escape key and Android’s back button close the message. The bridge fires sequenzy:ready (and Braze’s ab.BridgeReady) on window once the document has loaded. Every call acts on the contact who is signed in to the app. A message cannot change who the contact is, read other contacts, or reach your app’s own storage. Each message can make up to 50 bridge calls each time it is shown, and each call is limited to 32 KB. Events and attributes follow the same rules as the SDKs’ track and setProfile: event names start with a letter or number and are up to 100 characters, properties are up to 8 KB, and a contact can get up to 60 event batches and 30 attribute updates a minute. Calls the server rejects are logged in the console on the web.

Braze compatibility

changeUser, setEmail, setPhoneNumber, subscription groups, array and increment attributes, logPurchase and requestPushPermission do nothing and log a warning in the console: a message can’t change identity or ask for push permission. Braze Liquid personalization is converted to Sequenzy merge tags when you save: Liquid without an equivalent, such as {% if %} blocks, Connected Content or {{${country}}}, has to be replaced before you can save; the editor names each one.

Survicate surveys

Survicate’s Braze In-App Message survey works as is:
  1. In Survicate, create the survey with the Braze In-App Message type and copy the code from its Launch tab.
  2. Paste it into an HTML in-app step. Sequenzy converts {{${braze_id}}} and {{${email_address}}}, which Survicate needs to show the survey.
  3. Survicate’s script closes the message when the survey is finished or closed. Keep the close button on: if an ad blocker stops Survicate’s script, it is the way out.
Answers are stored in Survicate. To get them into Sequenzy as well, pick Insert → Forward Survicate answers in the editor. It adds a short script that logs each answer as a survicate_question_answered event (with surveyId, questionId, answerType and answer) and each finished survey as survicate_survey_completed, so you can segment on answers and start sequences from them. It uses Survicate’s JavaScript event listeners, which need a Survicate plan with JavaScript targeting. On the web, Survicate’s survey floats in the corner and the rest of your page stays usable.

On the web

Add data-sequenzy-in-app to the browser SDK snippet. It loads a separate 3 KB (gzipped) script, sequenzy-in-app.js, only on sites that use it:
HTML messages show for the contact you identify with sequenzy.identify(email, identityToken). The SDK checks the feed on page load, after identify, when the tab becomes visible, and every minute. After sequenzy.reset() or an identify for someone else, a message on screen for the previous contact is removed. With the npm package:
The web SDK shows custom HTML messages only. For modals, banners and cards on the web, fetch the feed and draw them with your own components. Content Security Policy. The message is an iframe with srcdoc, which uses your page’s Content Security Policy. If your site sets one, allow https://api.sequenzy.com in script-src and connect-src, and allow the hosts your message loads from (the editor lists them). For Survicate that is https://*.survicate.com and https://*.survicate-cdn.com in script-src, style-src, font-src, img-src and connect-src. If your policy uses a nonce, add it to the snippet’s <script> tag; the SDK copies it onto the sequenzy-in-app.js tag and the message’s inline scripts and styles. A message whose script your policy blocks is skipped (with a console warning) instead of covering the page.

Security

  • Web: the message runs in an iframe sandboxed without allow-same-origin, so it has an opaque origin of its own (servers it calls see Origin: null). It can run scripts, submit forms and open new tabs, but it cannot read your page, its cookies, local storage or IndexedDB, and it cannot navigate your page. It gets its own in-memory storage.
  • iOS: a WKWebView with a private, non-persistent data store, so the message shares no cookies, storage or cache with your app’s other web views.
  • Android: a WebView with file and content access, mixed content and third-party cookies off.
  • React Native: react-native-webview in incognito mode with shared and third-party cookies off.
  • Mobile: the document loads at https://in-app.sequenzy.com, never your app’s origin. Each time it is shown, its local storage, session storage and cookies are cleared before your scripts run, so a survey’s saved state doesn’t carry over from one contact to the next on a shared device. iOS and React Native also use a private web view store.
  • Everywhere: navigation away from the document opens through your app’s URL handler, which only accepts https links and app deep links; javascript:, data:, file: and plain http: links are blocked.
  • A message that does not finish loading within 10 seconds is not shown, is not counted as displayed, and stays in the feed for a later visit. Until it has loaded it does not block taps on your app.
Only people who can edit sequences can create HTML messages. The HTML runs in your own app’s users’ devices, so treat it like code you ship: use scripts from providers you trust.

Limits

A step is skipped for a contact (in_app.skipped with reason html_too_large) when merge tags make their copy larger than 128 KB.

Results

HTML messages count like other in-app messages: created, displayed, clicked and dismissed per step. A click logged with a button ID (logClick("submit")) records the ID on the contact’s in_app.clicked activity.

CLI and MCP

The API equivalent of the check is Validate In-App HTML.