OneSignal: Custom Newsletter Forms
If you’ve built your own newsletter capture — a popup, an inline banner, a footer signup, anything that isn’t a stock Shopify form — you can have Vendo route that email through to OneSignal as a real Email subscription without writing any OneSignal code yourself.
This article covers the two integration paths, what Vendo does automatically, where the email lands in OneSignal, and how to test the flow end-to-end.
Prerequisites
Before any of this works, three things must be true on the storefront:
- The Vendo Shopify app is installed and the OneSignal destination is configured (App ID entered in Vendo > Integrations > OneSignal).
- The Vendo theme app embed is enabled in the active theme: Online Store > Themes > Customize > App embeds > Vendo: ON. This is what loads the OneSignal Web SDK and Vendo’s newsletter listener.
- You have decided on a
user_id_typein the Vendo OneSignal config — usuallyshopify_customer_id. This determines how Vendo identifies the user in OneSignal once the Shopify customer record exists.
Without (2) in particular, your form will submit fine to your own backend but nothing will reach OneSignal.
Path A — Auto-detection (recommended)
The simplest integration. You write a standard HTML form with two specific traits, and Vendo’s theme listener does the rest.
The contract
Vendo’s storefront listener scans the page for forms matching any of these selectors:
form[action^="/contact#"]
form.newsletter-form
form.klaviyo-form
form[id*="newsletter" i]
form[class*="newsletter"]For each matched form, Vendo attaches a submit event listener that reads the value from input[type="email"] inside the form. So your form must:
- Be a real
<form>element (not a<div>with an input). - Have an
idorclasswhose name containsnewsletter(or match one of the other selectors above). - Contain an
<input type="email">.
That’s all. Your submit handler can call event.preventDefault() and post the email to your own backend via fetch() — Vendo’s listener fires on the submit event itself, before preventDefault matters.
What Vendo does when it detects a submission
- Captures the email and writes a record into
localStorage.vendo_identitywith_pending_event = 'newsletter_registered'. - Calls
OneSignal.User.addTags({ email })against the SDK that the Vendo theme embed loaded — sets theemailtag on the current OneSignal user. - Calls
OneSignal.User.addEmail(email)— creates a real Email subscription channel on that user, withenabled: true. The user becomes sendable via email. - If
user_id_type = 'shopify_customer_id', polls/apps/vendo/customer-lookup?email=...(the Vendo app proxy) att=0, 2s, 6s, 14s. When Shopify returns a customer ID for that email, Vendo callsOneSignal.login(<shopify_customer_id>)— this transfers the user (with the email subscription already attached) onto the customer-ID-keyed record. - If the customer doesn’t exist yet (typical for first-time signups), Vendo persists
_vendo_pending_lookupin localStorage and retries on each subsequent page load (up to 5 cross-page attempts). - On the next Vendo pixel event after submission, fires a
Newsletter Registeredevent into every connected destination (OneSignal custom event, Mixpanel, Segment, Customer.io, etc.) with the email attached.
Minimal example
A working inline newsletter banner that satisfies the contract:
<form class="newsletter-form" id="my-newsletter">
<input type="email" name="email" placeholder="you@example.com" required />
<button type="submit">Subscribe</button>
</form>
<script>
document.getElementById('my-newsletter').addEventListener('submit', function (e) {
e.preventDefault();
var email = e.target.querySelector('input[type="email"]').value.trim();
if (!email) return;
// Post to your own backend — Vendo has already captured the email by this point.
fetch('/your/backend/endpoint', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email })
});
});
</script>That’s it. No OneSignal App ID anywhere in your code — Vendo provides it from the shop metafield. No OneSignal SDK loading — Vendo loads it. No identify calls — Vendo handles identity, including the deferred customer-ID resolution.
What lands in OneSignal
A single user record (the existing push subscriber’s record if the visitor has previously allowed push, or a brand-new anonymous user otherwise) with:
- An Email subscription channel with
token = <email>andenabled = true - A tag
email = <email> - After the customer-lookup resolves:
external_id = <shopify_customer_id>
The email subscription is attached to the existing user record — it does not create a separate “email-only” user. This is intentional and keeps push + email + future purchases all merged on one user record.
Path B — Manual identification
Use Path B when:
- Your popup is not a
<form>(it’s a<div>with inputs) - You can’t add
newsletterto the class/id (theming constraints) - You want to capture the email at a different point than form submit (e.g. on a multi-step modal’s “next” button)
Path B writes the same Vendo data structure that Path A writes — just from your own code instead of via auto-detect.
What to write
function vendoCaptureNewsletter(email) {
var data = JSON.parse(localStorage.getItem('vendo_identity') || '{}');
data.email = email;
data.newsletter_subscribed = true;
data.newsletter_subscribed_at = new Date().toISOString();
data._pending_event = 'newsletter_registered';
localStorage.setItem('vendo_identity', JSON.stringify(data));
// Triggers the redirect-page customer-ID lookup on the next page load
localStorage.setItem('_vendo_nl_email', email);
}This alone fires Newsletter Registered into all destinations on the next pixel event. It does not by itself attach an Email subscription to the OneSignal user — for that, also call:
window.OneSignalDeferred = window.OneSignalDeferred || [];
window.OneSignalDeferred.push(function (OneSignal) {
try {
OneSignal.User.addTags({ email: email });
OneSignal.User.addEmail(email);
// Do NOT call OneSignal.login(email) when user_id_type = 'shopify_customer_id'.
// Vendo will call login() with the resolved customer_id after the lookup completes.
} catch (e) { /* push permission may be blocked — addEmail still works */ }
});Then call both functions from your custom UI:
yourCustomPopup.onSubmit(function (email) {
vendoCaptureNewsletter(email);
// (the OneSignalDeferred block above runs once per page load, not per submit)
// ...your own backend POST here
});OneSignal.User.addEmail() is idempotent, so even if your code and Vendo’s auto-detect both run on the same submit there’s no harm — both calls hit the same subscription endpoint and the second returns 409, which Vendo treats as success.
Important: do not call OneSignal.login(email)
With user_id_type = 'shopify_customer_id', the only valid argument to OneSignal.login() is the Shopify customer ID. Calling login(email) burns the email as a permanent external_id alias that OneSignal v16 cannot rename later — the user record becomes orphaned from the customer record at checkout.
Vendo defers login() until the customer lookup resolves to a real Shopify customer ID. Let it.
What Vendo does not do
| Concern | Owner |
|---|---|
| GDPR / CAN-SPAM consent capture, storage, audit trail | You — Vendo does not set any consent_status or opt-in timestamp |
| Backend email storage (for re-engagement, exports, etc.) | You — the fetch() to your own endpoint is yours alone |
| Email validation / disposable-domain filtering | You (or your backend) |
| Double opt-in confirmation emails | You / OneSignal Journeys |
| Unsubscribe link / preferences page | You / OneSignal |
For consent in particular, the recommended pattern is to set your own tags from the same submit handler:
OneSignal.User.addTags({
marketing_consent: 'granted',
marketing_consent_at: new Date().toISOString(),
marketing_consent_source: 'site_newsletter_popup_v1'
});This persists alongside the email subscription and gives you an auditable property to filter on in OneSignal segments.
Where to find the user in the OneSignal dashboard
After a test submission:
- Open the dashboard at
https://dashboard.onesignal.com/apps/<your-app-id>/users. Make sure the App ID matches the one configured in Vendo — newsletter signups go to whichever app you’ve configured, and it’s easy to be looking at the wrong app. - In the User Records page, change the search filter dropdown from External ID to Email.
- Search for the test email. The user appears with an Email channel and
emailtag matching the test value. - If the visitor was already a push subscriber on this browser, the email subscription is attached to that same user record — the Users count doesn’t increment, the user simply gains another subscription channel.
- Until the Shopify customer record for that email exists,
external_idstays empty (or stays whatever it was from previous identification on that browser). Once Vendo’s customer-lookup resolves the Shopify customer ID,external_idupdates to the Shopify customer ID and the user merges with any other identification of the same customer.
Why your tests sometimes look like “no user was created”
The most common confusions, in order of frequency:
- Wrong app. Multiple OneSignal apps in the same OneSignal account, looking at the wrong one. Confirm the dashboard URL’s App ID matches
OneSignal.config.appIdin the storefront’s DevTools console. - Email filter not selected. The Users page defaults to filtering by External ID — a search for the email returns nothing because External ID = email isn’t true with
user_id_type = 'shopify_customer_id'. - Test email isn’t a real Shopify customer. Vendo never sets
external_id = emailin customer-ID mode, so the user record hasexternal_id = null(or stale from prior tests) until either you manually create a Shopify customer with that email, or the visitor goes through checkout. - Dashboard indexing delay. The REST API accepts the subscription within milliseconds (HTTP 201), but the dashboard Users view can take a couple of minutes to surface a new subscription.
Testing the flow
- Open the storefront in an incognito window and append
?vendo_debug=1to the URL. See Debug Mode for what to expect in the console. - Open DevTools > Network and filter to
onesignal.com. - Submit your custom form with a fresh test email.
- You should see, in order:
- Console:
[Vendo] Newsletter email captured: <email> - Console:
[Vendo] Newsletter: resolving customer ID... - Console:
[Vendo] Newsletter: OneSignal anonymous — email subscription added - Network:
PATCH https://api.onesignal.com/apps/<app_id>/users/by/onesignal_id/<onesignal_id>returning 202 (setsemailtag) - Network:
POST https://api.onesignal.com/apps/<app_id>/users/by/onesignal_id/<onesignal_id>/subscriptionsreturning 201 (creates Email subscription, response body contains the new subscription’s id)
- Console:
- Open OneSignal dashboard at
https://dashboard.onesignal.com/apps/<app_id>/users, set the filter to Email, search the test email — the user is there with an Email channel.
If you want to confirm the App ID Vendo is actually sending to (vs. what’s displayed in the Vendo admin), run this in the storefront’s DevTools console:
OneSignal.config.appIdThat returns the App ID the SDK initialised with on this page — i.e. the live destination of any newsletter submission.
Troubleshooting
My form submits but I see no [Vendo] Newsletter email captured log
- The Vendo theme app embed is off. Re-enable it: Online Store > Themes > Customize > App embeds > Vendo: ON > Save.
The log appears but no OneSignal API requests fire in the Network tab
- The OneSignal SDK didn’t load. Check the OneSignal destination is saved in the Vendo admin (App ID entered, saved). Reload the storefront.
- Push permission blocked at the browser level — that’s fine for push, but
addEmail()should still fire. Check the storefront DevTools console for OneSignal errors.
The user appears in OneSignal but with external_id = <some UUID I don't recognise>
- That UUID was set by an earlier manual
OneSignal.login(...)call on this browser. Vendo doesn’t set arbitrary UUIDs asexternal_id. To reset for a clean test, runOneSignal.logout()in the storefront’s DevTools console and reload.
The customer-lookup never resolves
- The email submitted has no matching Shopify customer record yet. Either complete checkout with the same email, or create the customer manually in Shopify admin (Customers > Add customer). Then refresh the storefront — the deferred path will pick it up within a couple of page loads.
Two separate users appear in OneSignal — one keyed by email, one by customer_id
- Something in the theme is calling
OneSignal.login(email). Search the active theme forOneSignal.login(and remove any non-Vendo call. Vendo never callslogin()with an email whenuser_id_type = 'shopify_customer_id'.
Related
- OneSignal Integration — full OneSignal destination setup, events, tags, and payloads
- Debug Mode — how to inspect every event Vendo fires from the storefront
- Sending Custom Events — sending arbitrary events via Shopify’s Web Pixel API
Support
If your custom form integration isn’t behaving as expected, enable Debug Mode, reproduce the issue, and email the console output plus the screenshot of the OneSignal Users page (with the Email filter applied) to support@vendodata.com.