Help in your app
Your customers can write in, and follow their tickets, from inside your own mobile app. For now your app shows a page of your website in a web view. Your website's verified domain is what lets your live key work, as it does on the website itself. A React Native app does it with react-native-webview, below. Any other app does the same with its own web view.
In the portal, add a site and choose Mobile app where it asks how the site is built. Its Install tab has these steps with your key, your domain's proof and your website's address filled in.
1. Show the website is yours
Give your website's address, then add the line the Install tab shows inside <head> on its home page, and press Check your site. A DNS record or a file works too, under Settings → Domains. Until the domain is verified, use your test key. It works anywhere, and nothing sent with it counts or emails anyone.
2. A page on your website for the app
Make a page on your website that holds only the form and the user's own tickets, at an address like https://acme.com/app-support:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Support</title>
<script src="https://help.deltabreen.eu/embed/v1.js" data-help-key="pk_live_…" async></script>
</head>
<body>
<db-help-form></db-help-form>
<db-help-tickets></db-help-tickets>
</body>
</html>Style it as you would any page of your site. The form's own words, fields and colour are on the site's Form tab.
3. Open it in your app
A screen of your app shows the page:
// npx expo install react-native-webview (an Expo app)
// npm install react-native-webview (any other, then: cd ios && pod install)
import { Linking } from 'react-native'
import { WebView } from 'react-native-webview'
const SITE = 'https://acme.com'
const PAGE = SITE + '/app-support'
// token: the logged-in user's identity token, from your server. Leave it out for a guest.
export function Support({ token }: { token?: string }) {
return (
<WebView
source={{ uri: PAGE }}
// The form opens as that user, from the moment the page loads.
injectedJavaScriptBeforeContentLoaded={token ? 'window.DeltaHelpIdentity = ' + JSON.stringify(token) + '; true;' : undefined}
// Your site stays in the app. Anything else, like a file in a reply, opens outside it.
onShouldStartLoadWithRequest={(request) => {
if (request.url.startsWith(SITE + '/') || request.url.startsWith('about:')) return true
void Linking.openURL(request.url)
return false
}}
onOpenWindow={(event) => void Linking.openURL(event.nativeEvent.targetUrl)}
/>
)
}The form's button for adding a file opens the phone's own picker. A file in one of your team's replies opens in the phone's browser, which saves or shows it.
Logged-in users
A user who is logged in to your app can write in without typing their email, and see their own tickets and your team's replies. As on your website, your server says who they are, by signing a short token with your site's secret (Logged-in users has the code). Your app asks your server for one when the screen opens, and hands it to the page before it loads:
const token = await fetch('https://api.acme.com/help-token', { headers: { authorization: 'Bearer ' + session } }).then((r) => r.text())
<Support token={token} />Sign it for as long as the screen may stay open. An hour is plenty, and 24 hours is the most we take. Ask for a new one each time the screen opens. When they log out, show the screen without one, and the form treats them as a guest.
Your app has no website
Then your app's screen carries the page itself, so there's nothing to host, and no page's address is needed for your live key. In the portal, choose Mobile app, then My app has no website under the address. Your live key then works for two kinds of people, and nobody else:
- People logged in to your app. Your server signs each one's token with your site's secret, as above.
- Anyone with your app, if you switch it on. The app makes a key for the phone the first time support opens, and keeps it there. That key is who they are to us: they write in and read your replies on that phone, with no account and no email. We keep only the key's fingerprint. A new phone, or the app installed again, starts fresh.
We recommend signing your users in through your own server where you can. With only Anyone with your app on, your app's key is all it takes to write in, and a key inside an app can be taken out of it. Someone who does can send your team tickets as a new phone each time. Each one costs them a little work, and the limits on each phone, each network address and your whole site still hold, but only logged-in users can't be made up. If unwanted tickets arrive, mark them as spam, and switch Anyone with your app off until your server signs users in.
// npx expo install react-native-webview expo-secure-store expo-crypto
import * as Crypto from 'expo-crypto'
import * as SecureStore from 'expo-secure-store'
import { useEffect, useState } from 'react'
import { Linking } from 'react-native'
import { WebView } from 'react-native-webview'
// The form's page, inside your app: your site's key and nothing secret.
const PAGE = '<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">' +
'<script src="https://help.deltabreen.eu/embed/v1.js" data-help-key="pk_live_…" async></script></head>' +
'<body><db-help-form></db-help-form><db-help-tickets></db-help-tickets></body></html>'
// This phone's own key for Help: made once, kept on the phone.
async function phoneKey(): Promise<string> {
const kept = await SecureStore.getItemAsync('deltabreen-help-key')
if (kept) return kept
const made = Array.from(Crypto.getRandomBytes(32), (b) => b.toString(16).padStart(2, '0')).join('')
await SecureStore.setItemAsync('deltabreen-help-key', made)
return made
}
// token: a logged-in user's identity token from your server, if you have one; anyone else writes in by their phone.
export function Support({ token }: { token?: string }) {
const [phone, setPhone] = useState<string | null>(null)
useEffect(() => void phoneKey().then(setPhone), [])
if (!token && !phone) return null
const handed = token ? 'window.DeltaHelpIdentity = ' + JSON.stringify(token) : 'window.DeltaHelpDevice = ' + JSON.stringify(phone)
return (
<WebView
source={{ html: PAGE }}
injectedJavaScriptBeforeContentLoaded={handed + '; true;'}
// The page stays in the app. Anything else, like a file in a reply, opens outside it.
onShouldStartLoadWithRequest={(request) => {
if (request.url.startsWith('about:')) return true
void Linking.openURL(request.url)
return false
}}
onOpenWindow={(event) => void Linking.openURL(event.nativeEvent.targetUrl)}
/>
)
}Not using Expo? Any random bytes and secure storage will do: the key is 32 to 128 letters, digits, hyphens or underscores, and the same key on every visit.
A site with no website sends no email unless you verify its support address in the site's Settings. Your customers read your replies in the app, and DeltaHelp.unread() gives you a number to show beside your Support button. To tell them sooner, a webhook (Pro and higher) can push your app's own notice when your team replies.
Good to know
- A ticket from your app is one of your site's tickets, sent from the page your app showed.
- The emails your customers get link to a page of ours, which opens in their phone's browser.
- Another kind of app (Flutter, Swift, Kotlin): open the same page in its web view. For a logged-in user, set
window.DeltaHelpIdentityto the token before the page's scripts run, or callDeltaHelp.identify(token)once the page has loaded. With no website, load the page's HTML into the web view and setwindow.DeltaHelpIdentityorwindow.DeltaHelpDevicethe same way.