# Introduction > In-app support for iOS apps and websites. Users write in from inside your product; replies land back where they asked. Source: https://feddy.app/docs/ Feddy gives the people using your app or website a place to write in, and gives you one inbox to answer from. Replies show up inside the product, and by email when you have set up a sending domain. An assistant answers repeat questions from answers you approved, and hands everything else to you. ## Before you start ## Add Feddy to your product Pick your platform. Each page takes about five minutes and ends with a message in your inbox. ## Learn how it works ## Go further - [Identify users](/docs/guides/identify-users/) so you know who wrote in and replies can reach them by email. - [Unread badge](/docs/guides/unread-badge/) on the row that opens Feddy, or anywhere else. - [iOS SDK reference](/docs/reference/ios/) and [Web SDK reference](/docs/reference/web/) for every call and option. - [Troubleshooting](/docs/troubleshooting/) when something does not behave as expected. --- # Create a project > One project per app or website, and where to find its public project ID. Source: https://feddy.app/docs/getting-started/create-a-project/ Everything the SDK sends is tied to a project. Create one before you install anything. ## Sign up Create an account at [dash.feddy.app](https://dash.feddy.app/signup). The free plan has no time limit and is enough to finish every page in these docs. ## Create the project A project is one app or website and everything that comes from it: conversations, contacts, topics, answers, and settings. Create one per product. An iOS app and its website can share a project if the same people answer both; use two if you want separate inboxes. ## Copy the project ID Open **Settings → Install**. The project ID looks like `fd_` followed by 16 characters. Every quickstart asks for it. The project ID is public by design. It ships inside your binary and in your page source, so anyone can read it. Commit it, hard-code it, put it in a public repository. There is no way to rotate it and no need to; abuse is handled by rate limits on the server. ## Development and production Use one project for both. The SDK's `apiUrl` option exists so you can point a development build at a local server; the project ID stays the same. Create a second project only if you want test conversations out of your real inbox. ## Next steps - Pick a platform on the [documentation home](/docs/) and follow its quickstart. - Read [Projects](/docs/concepts/projects/) for everything a project owns. --- # Set up with an AI agent > Hand the install to Claude Code, Cursor, or any coding agent that can read these docs. Source: https://feddy.app/docs/getting-started/ai-agent/ Every install in these docs is a few lines of code in a known place. An agent can do it for you if it knows your project ID and where the docs are. ## The prompt in your dashboard Every project's **Settings → Install** page has a prompt written for coding agents. It contains your project ID and the steps for your platform, so you can paste it into the agent as is. Read what the agent changed before you ship it; the [quickstart](/docs/) for your platform shows what a correct install looks like. ## Where agents can read these docs - **Context7**: the docs are indexed from the public [FeddyLab/feddy-docs](https://github.com/FeddyLab/feddy-docs) repository. Agents with the Context7 tool can look up `feddy` directly. - **llms.txt**: [feddy.app/llms.txt](https://feddy.app/llms.txt) lists every page with a one-line summary; [feddy.app/llms-full.txt](https://feddy.app/llms-full.txt) is the full text of every page in one file. - **The SDK sources**: the iOS package at [FeddyLab/feddy-ios](https://github.com/FeddyLab/feddy-ios) is public. Every call the docs mention is in there. ## What to check afterwards - `configure` or `init` runs once, with your project ID, before any other Feddy call. - The entry point is somewhere users already look for help, usually Settings. - A message sent from a debug build shows up in your inbox. Reply to it and the reply shows up in the app. --- # SwiftUI > Add Feddy to a SwiftUI app with Swift Package Manager and send your first message. Source: https://feddy.app/docs/quickstart/swiftui/ By the end of this page your app has a support entry point, and a message sent from it shows up in your Feddy inbox. ## Prerequisites - A Feddy project and its project ID from **Settings → Install**. [Create a project](/docs/getting-started/create-a-project/) if you have none. - Xcode 15 or later. The SDK targets iOS 15 and has no third-party dependencies. ## Install In Xcode choose **File → Add Package Dependencies…** and paste: ``` https://github.com/FeddyLab/feddy-ios ``` Or add it to `Package.swift`: ```swift .package(url: "https://github.com/FeddyLab/feddy-ios", from: "0.7.0") ``` ## Initialize Call `configure` once, before any other Feddy call. Later calls are ignored. ```swift title="App.swift" import Feddy @main struct MyApp: App { init() { Feddy.configure(projectId: "fd_XXXXXXXXXXXXXXXX") } var body: some Scene { WindowGroup { ContentView() } } } ``` The project ID ships inside your binary and is public by design. Commit it. Do not put it in a secrets file or an environment variable. ## Add an entry point Put a row where users already look for help, usually in Settings: ```swift Form { Button("Support") { Feddy.present() } .feddyUnreadBadge() } ``` `present()` opens the conversation list as a sheet over whatever is on screen. `presentNewConversation()` skips the list and opens the compose form. ## Verify Run the app, open the row, and send a message. It appears in your Feddy inbox. Reply from the inbox; the reply shows up in the sheet and the row's badge shows `1` until the user reads it. ## Next steps - [Identify users](/docs/guides/identify-users/) so replies can also reach them by email and you see who wrote in. - [Unread badge](/docs/guides/unread-badge/) for a custom row or a tab bar item. - [iOS SDK reference](/docs/reference/ios/) for every public call. --- # UIKit > Add Feddy to a UIKit app with Swift Package Manager and send your first message. Source: https://feddy.app/docs/quickstart/uikit/ By the end of this page your app has a support entry point, and a message sent from it shows up in your Feddy inbox. ## Prerequisites - A Feddy project and its project ID from **Settings → Install**. [Create a project](/docs/getting-started/create-a-project/) if you have none. - Xcode 15 or later. The SDK targets iOS 15 and has no third-party dependencies. ## Install In Xcode choose **File → Add Package Dependencies…** and paste: ``` https://github.com/FeddyLab/feddy-ios ``` Or add it to `Package.swift`: ```swift .package(url: "https://github.com/FeddyLab/feddy-ios", from: "0.7.0") ``` ## Initialize Call `configure` once, before any other Feddy call. Later calls are ignored. ```swift title="AppDelegate.swift" import Feddy func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { Feddy.configure(projectId: "fd_XXXXXXXXXXXXXXXX") return true } ``` The project ID ships inside your binary and is public by design. Commit it. Do not put it in a secrets file or an environment variable. ## Add an entry point Call `present()` from wherever users look for help, usually a row in Settings or a bar button item. It presents the conversation list as a sheet over the current view controller. ```swift title="SettingsViewController.swift" @objc func helpTapped() { Feddy.present() } ``` To show unread replies on the control, assign `onUnreadCountChanged`. It is called on the main thread whenever the count changes: ```swift Feddy.onUnreadCountChanged = { count in helpItem.badgeValue = count > 0 ? "\(count)" : nil } ``` ## Verify Run the app, tap the control, and send a message. It appears in your Feddy inbox. Reply from the inbox; the reply shows up in the sheet and the badge shows `1` until the user reads it. ## Next steps - [Identify users](/docs/guides/identify-users/) so replies can also reach them by email and you see who wrote in. - [Unread badge](/docs/guides/unread-badge/) for refreshing on foreground and other placements. - [iOS SDK reference](/docs/reference/ios/) for every public call. --- # JavaScript > Add the Feddy widget to any page with one script tag and send your first message. Source: https://feddy.app/docs/quickstart/javascript/ By the end of this page your site has a support launcher, and a message sent from it shows up in your Feddy inbox. ## Prerequisites - A Feddy project and its project ID from **Settings → Install**. [Create a project](/docs/getting-started/create-a-project/) if you have none. - A page you can edit. There is no build step and nothing to install from npm. ## Install Load the widget before the closing `` tag: ```html title="index.html" ``` The file is a single self-contained script with no dependencies. It renders inside a shadow root, so your CSS and the widget's never touch. ## Initialize ```html ``` `init` runs once per page; later calls are ignored. If the script loads before the DOM is ready, the widget mounts on `DOMContentLoaded`. The project ID ships in your page source and is public by design. Commit it. Do not put it in a secrets file or an environment variable. ## Choose an entry point By default a launcher appears bottom-right. If your site already has a **Support** or **Feedback** control, turn the launcher off and open the panel from your own element: ```html ``` ## Verify Open the page, click the launcher, and send a message. It appears in your Feddy inbox. Reply from the inbox; the reply shows up in the panel, and the launcher shows an unread count until the visitor reads it. ## Next steps - [Identify users](/docs/guides/identify-users/) so replies can also reach them by email and you see who wrote in. - [Unread badge](/docs/guides/unread-badge/) to drive your own badge from `onUnreadCountChanged`. - [Web SDK reference](/docs/reference/web/) for every option and call. --- # React > Load the Feddy widget from a React component and open it from your own button. Source: https://feddy.app/docs/quickstart/react/ By the end of this page your app has a support button, and a message sent from it shows up in your Feddy inbox. ## Prerequisites - A Feddy project and its project ID from **Settings → Install**. [Create a project](/docs/getting-started/create-a-project/) if you have none. - Nothing to install from npm. The widget is one script served from `core.feddy.app`, loaded at runtime. ## Add a component Load the script once when the component mounts, then initialize with the launcher off so your own button is the entry point: ```jsx title="Support.jsx" import { useEffect } from 'react' export function Support() { useEffect(() => { const el = document.createElement('script') el.src = 'https://core.feddy.app/sdk/feddy.js' el.onload = () => Feddy.init({ projectId: 'fd_XXXXXXXXXXXXXXXX', launcher: false }) document.body.append(el) }, []) return } ``` `init` runs once per page; if the effect runs twice in development, the second call is ignored. Leave out `launcher: false` to get the floating launcher instead of your own button. The project ID ships in your bundle and is public by design. Commit it. Do not put it in a secrets file or an environment variable. ## Render it Render `` once, somewhere that stays mounted, such as your app shell or settings page. ## Verify Open the page, click the control, and send a message. It appears in your Feddy inbox. Reply from the inbox; the reply shows up in the panel. ## Next steps - [Identify users](/docs/guides/identify-users/) so replies can also reach them by email and you see who wrote in. - [Unread badge](/docs/guides/unread-badge/) to drive your own badge from `onUnreadCountChanged`. - [Web SDK reference](/docs/reference/web/) for every option and call. --- # Vue > Load the Feddy widget in onMounted and open it from your own button. Source: https://feddy.app/docs/quickstart/vue/ By the end of this page your app has a support button, and a message sent from it shows up in your Feddy inbox. ## Prerequisites - A Feddy project and its project ID from **Settings → Install**. [Create a project](/docs/getting-started/create-a-project/) if you have none. - Nothing to install from npm. The widget is one script served from `core.feddy.app`, loaded at runtime. ## Add a component Load the script once when the component mounts, then initialize with the launcher off so your own button is the entry point: ```vue title="Support.vue" ``` The template cannot reach the `Feddy` global directly, which is why `open` is declared in the script. Leave out `launcher: false` to get the floating launcher instead of your own button. The project ID ships in your bundle and is public by design. Commit it. Do not put it in a secrets file or an environment variable. ## Render it Render `` once, somewhere that stays mounted, such as your app shell or settings page. ## Verify Open the page, click the control, and send a message. It appears in your Feddy inbox. Reply from the inbox; the reply shows up in the panel. ## Next steps - [Identify users](/docs/guides/identify-users/) so replies can also reach them by email and you see who wrote in. - [Unread badge](/docs/guides/unread-badge/) to drive your own badge from `onUnreadCountChanged`. - [Web SDK reference](/docs/reference/web/) for every option and call. --- # Svelte > Load the Feddy widget in onMount and open it from your own button. Source: https://feddy.app/docs/quickstart/svelte/ By the end of this page your app has a support button, and a message sent from it shows up in your Feddy inbox. ## Prerequisites - A Feddy project and its project ID from **Settings → Install**. [Create a project](/docs/getting-started/create-a-project/) if you have none. - Nothing to install from npm. The widget is one script served from `core.feddy.app`, loaded at runtime. ## Add a component Load the script once when the component mounts, then initialize with the launcher off so your own button is the entry point: ```svelte title="Support.svelte" ``` Svelte 4 writes the handler as `on:click`. Leave out `launcher: false` to get the floating launcher instead of your own button. The project ID ships in your bundle and is public by design. Commit it. Do not put it in a secrets file or an environment variable. ## Render it Render `` once, somewhere that stays mounted, such as your root layout or settings page. ## Verify Open the page, click the control, and send a message. It appears in your Feddy inbox. Reply from the inbox; the reply shows up in the panel. ## Next steps - [Identify users](/docs/guides/identify-users/) so replies can also reach them by email and you see who wrote in. - [Unread badge](/docs/guides/unread-badge/) to drive your own badge from `onUnreadCountChanged`. - [Web SDK reference](/docs/reference/web/) for every option and call. --- # Next.js > Load the Feddy widget with next/script from a client component in your root layout. Source: https://feddy.app/docs/quickstart/nextjs/ By the end of this page your site has a support launcher, and a message sent from it shows up in your Feddy inbox. ## Prerequisites - A Feddy project and its project ID from **Settings → Install**. [Create a project](/docs/getting-started/create-a-project/) if you have none. - Nothing to install from npm. The widget is one script served from `core.feddy.app`, loaded at runtime. ## Add a client component `next/script` needs a client component for its `onLoad` callback: ```tsx title="app/support.tsx" 'use client' import Script from 'next/script' export function Support() { return ( ``` Put them before the closing `` tag of the layout every page uses. The project ID ships in your page source and is public by design. Commit it. Do not put it in a secrets file or an environment variable. ## Choose an entry point The floating launcher appears bottom-right. To use your own control instead, pass `launcher: false` to `init` and call `Feddy.open()` from it. With the `` view transitions enabled, the widget stays mounted across navigations. ## Verify Open the page, click the control, and send a message. It appears in your Feddy inbox. Reply from the inbox; the reply shows up in the panel. ## Next steps - [Identify users](/docs/guides/identify-users/) so replies can also reach them by email and you see who wrote in. - [Unread badge](/docs/guides/unread-badge/) to drive your own badge from `onUnreadCountChanged`. - [Web SDK reference](/docs/reference/web/) for every option and call. --- # WordPress > Add the Feddy widget to a WordPress site from your theme or a small plugin. Source: https://feddy.app/docs/quickstart/wordpress/ By the end of this page your site has a support launcher, and a message sent from it shows up in your Feddy inbox. ## Prerequisites - A Feddy project and its project ID from **Settings → Install**. [Create a project](/docs/getting-started/create-a-project/) if you have none. - Nothing to install from npm. The widget is one script served from `core.feddy.app`, loaded at runtime. ## Enqueue the script Add this to your theme's `functions.php`, or to a plugin file if you would rather not edit the theme: ```php title="functions.php" add_action('wp_enqueue_scripts', function () { wp_enqueue_script('feddy', 'https://core.feddy.app/sdk/feddy.js', [], null, true); wp_add_inline_script('feddy', "Feddy.init({ projectId: 'fd_XXXXXXXXXXXXXXXX' })"); }); ``` `true` puts the script in the footer; `null` as the version keeps WordPress from appending a cache-busting query string, so the file is cached the way the server intends. The project ID ships in your page source and is public by design. Commit it. Do not put it in a secrets file or an environment variable. ## Choose an entry point The floating launcher appears bottom-right on every page. To open the panel from a menu item or button instead, pass `launcher: false` to `init` and add a click handler that calls `Feddy.open()`. ## Verify Open the page, click the control, and send a message. It appears in your Feddy inbox. Reply from the inbox; the reply shows up in the panel. ## Next steps - [Identify users](/docs/guides/identify-users/) so replies can also reach them by email and you see who wrote in. - [Unread badge](/docs/guides/unread-badge/) to drive your own badge from `onUnreadCountChanged`. - [Web SDK reference](/docs/reference/web/) for every option and call. --- # Webflow > Add the Feddy widget to a Webflow site by pasting two lines into Custom code. Source: https://feddy.app/docs/quickstart/webflow/ By the end of this page your site has a support launcher, and a message sent from it shows up in your Feddy inbox. ## Prerequisites - A Feddy project and its project ID from **Settings → Install**. [Create a project](/docs/getting-started/create-a-project/) if you have none. - Nothing to install from npm. The widget is one script served from `core.feddy.app`, loaded at runtime. ## Paste into Custom code In Webflow open **Site settings → Custom code** and paste into **Footer code**: ```html title="Site settings → Custom code → Footer code" ``` Publish the site. Custom code is not applied in the Designer preview. The project ID ships in your page source and is public by design. Commit it. Do not put it in a secrets file or an environment variable. ## Choose an entry point The floating launcher appears bottom-right on every page. To open the panel from a button you designed, give the button an ID such as `help`, pass `launcher: false` to `init`, and add one line after it: `document.querySelector('#help').addEventListener('click', () => Feddy.open())`. ## Verify Open the page, click the control, and send a message. It appears in your Feddy inbox. Reply from the inbox; the reply shows up in the panel. ## Next steps - [Identify users](/docs/guides/identify-users/) so replies can also reach them by email and you see who wrote in. - [Unread badge](/docs/guides/unread-badge/) to drive your own badge from `onUnreadCountChanged`. - [Web SDK reference](/docs/reference/web/) for every option and call. --- # Projects > One project per app or website, identified by a public project ID. Source: https://feddy.app/docs/concepts/projects/ A project is one app or website and everything that comes from it: conversations, contacts, topics, answers, and settings. Create one project per product. An iOS app and its website can share a project if the same people answer both, or use two if you want separate inboxes. ## Project ID Every project has an ID of the form `fd_` followed by 16 characters. Copy it from **Settings → Install**. The SDK sends it with every request so the server knows which project a message belongs to. The ID ships inside your binary and in your page source, so anyone can read it. It is not a secret and never was: commit it, hard-code it, put it in a public repository. There is no way to rotate it and no need to. Abuse of a public ID is handled by rate limits on the server, not by hiding the ID. ## What a project owns - **Install**: the project ID and copy-paste snippets for iOS, web, and AI agents. - **General**: name, slug, and the brand shown to users: brand name, accent colour, and the reply-time promise shown before and after a user sends a message. - **Topics**: the choices users see when they start a conversation. - **Data attributes**: the fields you send with `identify`. - **Assistant** and **Knowledge**: automatic replies and the answers they draw on. - **Saved replies**: snippets you insert into the reply box by typing `/`. - **Email domain**: the domain replies are emailed from. - **Members**: who can see this project's inbox. ## Development and production Use one project for both. The SDK's `apiUrl` option exists so you can point a development build at a local server; the project ID stays the same. Create a second project only if you want test conversations out of your real inbox. --- # Conversations > How a message from a user becomes a conversation, what its status means, and what travels with it. Source: https://feddy.app/docs/concepts/conversations/ A conversation starts when a user sends a message from the SDK. It carries the message, the topic they picked, and what the SDK knows about their device. Everything that follows, replies, notes, status changes, is appended to it in order. ## Status | Status | Meaning | | --- | --- | | Open | The user is waiting on you. New conversations start here, and a user reply on a pending conversation returns it here. | | Pending | You are waiting on the user. Sending a reply moves the conversation here automatically. | | Closed | Done. Closed conversations are never unread. | You can set any status from the conversation header. Two things happen without you: - A conversation that stays pending for 14 days is closed automatically. - A user who answers "Was this helpful?" with yes after an assistant reply closes the conversation themselves. If a user writes again within 7 days of a close, the conversation reopens. After 7 days their message starts a new conversation, linked to the old one. ## Topics Users pick a topic when they start a conversation. Four are built in: **Bug**, **Feature request**, **Question**, and **Other**. The SDK shows them in the user's language. You can switch any of them off in **Settings → Topics** but cannot rename or delete them. Add your own topics for anything specific to your product, such as "Billing" or "Sync". A custom topic has a name, translations, and a code the SDK sends. The code cannot be changed once created. ## What travels with a conversation The SDK attaches what it can see, and the inbox shows it beside the thread: - iOS: device model, OS version, app version and build, locale. - Web: page URL, browser, OS, viewport, referrer, locale. Anything you send with `identify` appears as well. See [Contacts](/docs/concepts/contacts/). ## Replies and notes The reply box has two modes. **Reply** goes to the user and moves the conversation to pending. **Note** stays in the inbox for your team and changes nothing the user sees. Type `/` in the reply box to insert a saved reply. ## Attachments Users can attach up to 5 images per message, PNG, JPEG, or WebP, up to 10 MB each before the SDK resizes them. Other file types are not accepted. Attachments are available on paid plans. ## Unread A conversation is unread when nobody on your team has seen its latest message. Opening it marks it read for everyone. Use **Mark unread** in the header to bring it back. Closed conversations never count as unread. --- # Contacts > Who is writing in, from an anonymous device to an identified user with attributes. Source: https://feddy.app/docs/concepts/contacts/ A contact is one person, as far as Feddy can tell. Every conversation belongs to exactly one contact. ## Anonymous by default The first time the SDK runs it generates an anonymous id and stores it, in the Keychain on iOS and in `localStorage` on the web. That id is the contact. It survives app reinstalls on iOS. On the web it lasts until the visitor clears site data, which is why the widget can ask for an email after the first message. Anonymous contacts show as **Anonymous** in the inbox, with the device details the SDK collected. ## Identified Call `identify` with your own user id to turn the anonymous contact into a known one. The inbox then shows the user's name or email, and their conversations from every device merge into one history. How to call it, and what happens on merge, is in [Identify users](/docs/guides/identify-users/). ## What you know about a contact Two kinds of facts appear in the **Details** panel next to a conversation: - **Collected by the SDK**: device, OS, app version, language, first and last seen. You do not send these. - **From `identify`**: the attributes you chose to send, such as plan or membership status. **Settings → Data attributes** lists every attribute key you have ever sent, with its inferred type. There you can give a key a readable label, show it as a column in the inbox, or make it filterable. New keys appear automatically the first time the SDK sends them. ## Email A contact's email comes from `identify` or from the user typing it into the widget. It is used for one thing: emailing them your replies, and only when your project has a verified sending domain. See [Team](/docs/concepts/team/). ## Contacts page **Contacts** lists everyone who has written in, with their conversations and last-seen time. Filter to anonymous contacts only to see how many users you could identify. --- # Assistant > Automatic replies from answers you approve, and a Copilot that drafts replies for you. Source: https://feddy.app/docs/concepts/assistant/ The assistant does two jobs. It replies to users on its own, within limits you set, and it helps you write replies in the inbox. It never invents product facts: everything it says to a user comes from answers you wrote or approved. ## Answers An answer is a piece of text you would send in reply to a common question, with a title for your own reference and optional keywords, topic, and platform to narrow when it applies. Write them in **Settings → Assistant → Knowledge**, or import a batch. **About your app** on the same page is a short description the assistant reads before every reply. Fill it in; it is how the assistant knows what your product is. ## Modes Choose how the assistant replies to users in **Settings → Assistant**: | Mode | What users get | | --- | --- | | Keyword answers only | An answer whose keyword appears in the message. Everything else gets the fallback message. | | Answer with AI | A reply composed from your answers. Questions your answers do not cover are handed to you with the fallback message. | | Suggest replies to me | Nothing automatic beyond keyword answers. The AI drafts a reply for you in the inbox instead. | Switch **Enabled** off to send nothing automatically. In every mode the assistant leaves the conversation status alone and never marks it resolved. Only you, or the user, can close a conversation. ## Fallback message Sent when a new conversation gets no answer, so the user knows they were heard and roughly when to expect you. Leave it empty to send nothing. The default is a thank-you with a 24 to 48 hour expectation; edit it to match your real response time. ## AI instructions Free text applied to every AI reply: tone, what to call your product, what never to say. Keep facts in answers and use instructions for behavior. ## Was this helpful? After an assistant reply the user sees a **Was this helpful?** prompt. Yes closes the conversation as resolved by the user. No leaves it open and flags the reply in your inbox. **Results** on the Knowledge page shows how often replies were sent, rated helpful, resolved, or handed off. ## Learn from closed conversations When on, the assistant drafts new answers from conversations you close and lists them under **Suggested answers** for review. Nothing becomes an answer until you approve it. ## Copilot Next to every conversation, the **Copilot** tab has read the thread, your notes, and your answers. Ask it to draft a reply, summarize, translate into the user's language, or soften the tone. Its output goes into your reply box, where you edit and send it. Nothing it writes reaches the user until you send it. ## Try it **Try it** on the Knowledge page runs a message through the assistant as a user would see it, with a chosen topic, platform, and language. Nothing is sent. ## Availability AI replies and the Copilot are available on paid plans. Keyword answers and the fallback message work on every plan. --- # Team > Members, how you are notified, and how replies reach users by email. Source: https://feddy.app/docs/concepts/team/ ## Members A project's **Members** page lists who can see its inbox. There are two roles: - **Owner**: every project, plus members and billing. - **Member**: this project's inbox only. Invite by email. Invitations expire after 7 days. Owners can disable a member without removing them, and restore them later. ## Notifications Each person sets their own under **Account → Notifications**. - **Email**: a message for new conversations and replies, either immediately or combined every 30 minutes, 3 hours, or 12 hours. - **Dashboard**: a sound on new messages, the unread count in the browser tab title, and desktop notifications while you are away from the tab. Desktop notifications are a per-browser setting. ## Replies by email Replies always appear inside your app or site. They are also emailed to the user when two things are true: the contact has an email, and your project has a verified sending domain under **Settings → Email domain**. Add a subdomain such as `support.yourapp.com`, publish the DNS records shown, and replies go out from your own address with the full reply in the body. Until the domain is verified, replies are shown only in the app, and the SDK does not ask users for an email. Feddy never emails your users from a Feddy address. Mail to you and your team, such as notifications and invitations, comes from Feddy's own domain. ## Saved replies **Settings → Saved replies** holds snippets you send often. Give one a shortcut and type `/shortcut` in the reply box to insert it. --- # Identify users > Bind conversations to a logged-in user so you know who wrote in and replies can reach them by email. Source: https://feddy.app/docs/guides/identify-users/ By the end of this page conversations from a logged-in user show their name and attributes in the inbox, and their history follows them across devices. Every install works without this step. Until you call `identify`, each device is an anonymous contact whose id lives in the Keychain on iOS and in `localStorage` on the web. ## When to call it Call `identify` right after login, and again whenever an attribute you send changes. Calling it on every launch is fine; it is an upsert. ```swift Feddy.identify( userId: user.id, email: user.email, attributes: ["plan": user.plan, "is_member": user.isMember, "renews_at": user.renewsAt] ) ``` ```js Feddy.identify({ userId: user.id, email: user.email, attributes: { plan: user.plan, is_member: user.isMember }, }) ``` ## What happens on the server - The device's anonymous contact gets your `userId` as its external id, plus the email, name, avatar URL, and attributes you sent. - If a contact with the same `userId` already exists, this device's conversations move onto it and the duplicate anonymous contact is deleted. The user sees one history on every device they log in on. - Attributes replace the previous set as a whole. Send every attribute you want to keep on each call; a key you leave out is removed. Email, name, and avatar URL are kept when you omit them. ## Attributes Send flat key-value pairs about the user that help you answer faster: plan, membership status, trial end date, account age. Values may be strings, numbers, booleans, or dates as ISO 8601 strings. Anything over the limits below is dropped silently. The request never fails because of attributes, so a mistake in your integration cannot stop a user from writing in. | Limit | Value | | --- | --- | | Keys per contact | 20 | | Key length | 40 characters | | Value length | 255 characters after serialization | | Total size | 8 KB | | Value types | string, number, boolean, ISO 8601 date string | Nested objects and arrays are dropped. In the dashboard, **Settings → Data attributes** lists every key you have sent, with its inferred type. Give a key a label to change how it appears in the inbox, switch on **In list** to show it as a column, and **Filterable** to filter conversations by it. Attributes are for facts about the user. Device model, OS version, app version, and locale are collected by the SDK and shown with every conversation already. ## Email Pass `email` when you have it. When your project has a verified sending domain, replies are also emailed to the user, so they hear back even if they never reopen the app. Without a verified domain the email is stored but no mail is sent. If you do not pass an email, the widget may ask the user for one after their first message, and only when your project has a verified sending domain. --- # Unread badge > Show users that a reply is waiting, on the row that opens Feddy or anywhere else. Source: https://feddy.app/docs/guides/unread-badge/ By the end of this page the control that opens Feddy shows the number of unread replies and clears when the user reads them. The SDK keeps the count current on its own: iOS polls every 30 seconds, the web widget every 45 seconds and whenever the tab becomes visible. ## iOS ### A row in a `List` or `Form` ```swift Form { Button("Support") { Feddy.present() } .feddyUnreadBadge() } ``` This is the badge iOS itself puts on rows in Settings. Nothing is drawn at zero. ### A row that draws its own chevron A list badge is pinned to the trailing edge and would land outside your chevron. Place the marker yourself: ```swift HStack { Text("Support") Spacer() FeddyUnreadDot(showsCount: true) Image(systemName: "chevron.forward") } ``` Pass `showsCount: false` for a plain dot. ### A tab bar item or anything custom Observe the published count: ```swift struct RootTabs: View { @ObservedObject private var unread = FeddyUnread.shared var body: some View { TabView { SupportView() .tabItem { Label("Support", systemImage: "bubble.left") } .badge(unread.count) } } } ``` UIKit apps assign `Feddy.onUnreadCountChanged` and set `tabBarItem.badgeValue` from it. ### Refresh on foreground Polling pauses while the app is suspended. Call `Feddy.refresh()` when the app becomes active so the badge is right before the next poll: ```swift .onChange(of: scenePhase) { phase in if phase == .active { Feddy.refresh() } } ``` ## Web With the default launcher there is nothing to do; it shows the count itself. With `launcher: false`, feed your own element: ```js Feddy.init({ projectId: 'fd_XXXXXXXXXXXXXXXX', launcher: false }) const badge = document.querySelector('#help-badge') Feddy.onUnreadCountChanged = (count) => { badge.hidden = count === 0 badge.textContent = String(count) } ``` ## What counts as unread A reply is unread until the user opens the conversation it belongs to. Closed conversations never count. Opening the list alone does not clear anything. --- # iOS SDK reference > Every public call in the Feddy Swift Package. Source: https://feddy.app/docs/reference/ios/ The Swift Package exposes one namespace, `Feddy`, plus three SwiftUI helpers for unread counts. Everything else is internal. Requirements: iOS 15 or later, Swift 5.9 or later, no third-party dependencies. Package URL `https://github.com/FeddyLab/feddy-ios`, product `Feddy`. ## `Feddy.configure(projectId:apiURL:)` ```swift static func configure(projectId: String, apiURL: URL = URL(string: "https://core.feddy.app")!) ``` Call once at launch. Later calls are ignored. Starts fetching the project configuration and the unread count, and polls the unread count every 30 seconds while the app runs. - `projectId`: copied from **Settings → Install** in the dashboard. Public by design. - `apiURL`: leave the default in production. Point it at a local server during development. ## `Feddy.present()` ```swift @MainActor static func present() ``` Opens the conversation list as a page sheet over the top-most view controller. Works from SwiftUI and UIKit. Ignored if the sheet is already open. Logs `[Feddy] present() called before configure(projectId:)` and does nothing if called before `configure`. ## `Feddy.presentNewConversation()` ```swift @MainActor static func presentNewConversation() ``` Same as `present()`, but opens straight into the compose form. ## `Feddy.identify(userId:email:name:avatarUrl:attributes:)` ```swift static func identify(userId: String, email: String? = nil, name: String? = nil, avatarUrl: String? = nil, attributes: [String: Any] = [:]) ``` Binds the device's anonymous contact to a user of your app. Call it after login and whenever attributes change. Does nothing before `configure`. Details and merge rules are in [Identify users](/docs/guides/identify-users/). - `userId`: your stable id for the user, 1 to 128 characters. - `avatarUrl`: optional absolute `http(s)` URL of a picture you already have of the user, shown in the inbox. Anything else is ignored. - `attributes`: values may be `String`, `Bool`, any number, or `Date`. `Date` is sent as an ISO 8601 string. Other types are dropped on the device. Server-side limits are listed in [Identify users](/docs/guides/identify-users/). ## `Feddy.unreadCount(_:)` ```swift static func unreadCount(_ completion: @escaping (Int) -> Void) ``` Fetches the current number of unread replies. The completion runs on the main thread. ## `Feddy.refresh()` ```swift static func refresh() ``` Re-fetches the unread count now. Call it when your app returns to the foreground so a badge is current before the next poll. ```swift .onChange(of: scenePhase) { phase in if phase == .active { Feddy.refresh() } } ``` ## `Feddy.onUnreadCountChanged` ```swift @MainActor static var onUnreadCountChanged: ((Int) -> Void)? ``` Called on the main thread whenever the unread count changes. Use it for a UIKit badge; SwiftUI apps should use the helpers below instead. ## `View.feddyUnreadBadge()` ```swift func feddyUnreadBadge() -> some View ``` Adds the system row badge with the unread count and keeps it current. Nothing is drawn at zero. Badges are rendered only inside `List`, `Form`, and `TabView`; anywhere else the modifier does nothing. ## `FeddyUnreadDot` ```swift struct FeddyUnreadDot: View { init(showsCount: Bool = false) } ``` A red marker you place yourself, for rows that draw their own chevron or for places a list badge cannot go. Renders nothing at zero. Pass `showsCount: true` to draw the number inside the marker. ## `FeddyUnread` ```swift @MainActor final class FeddyUnread: ObservableObject { static let shared: FeddyUnread @Published private(set) var count: Int } ``` The unread count as an observable object, for custom SwiftUI badges. ```swift @ObservedObject private var unread = FeddyUnread.shared ``` ## Behavior you do not configure - **Identity**: an anonymous id is generated on first use and stored in the Keychain, so history survives reinstalls. - **Language**: the sheet ships in English, Simplified Chinese, Traditional Chinese, Japanese, Korean, German, French, Spanish, Brazilian Portuguese, and Russian, following the device language. Other languages fall back to English. Text you write in the dashboard is translated server-side per project settings. - **Attachments**: up to 5 images per message, resized to a 3000 px long edge before upload. The picker requires iOS 16; on iOS 15 the attachment row is absent. - **Branding**: name, accent color, and logo come from the dashboard, not from code. - **Privacy manifest**: the package ships a `PrivacyInfo.xcprivacy`. --- # Web SDK reference > Every option and call in the Feddy web widget. Source: https://feddy.app/docs/reference/web/ The widget is one script with no dependencies. It defines a global `Feddy` object with four methods and one callback. ```html ``` The script is served with a one-hour cache and open CORS, so it can be loaded from any origin. It renders inside a shadow root attached to `div#feddy-widget`, so your page styles and the widget's never affect each other. Keyboard events inside the widget do not reach your page. ## `Feddy.init(options)` ```js Feddy.init({ projectId: 'fd_XXXXXXXXXXXXXXXX' }) ``` Mounts the widget. Runs once per page; later calls are ignored. If called while the document is still loading, the widget mounts on `DOMContentLoaded`. Logs `[Feddy] init requires a projectId` and does nothing without a project ID. | Option | Type | Default | Description | | --- | --- | --- | --- | | `projectId` | `string` | required | Copied from **Settings → Install** in the dashboard. Public by design. | | `apiUrl` | `string` | `https://core.feddy.app` | Point at a local server during development. | | `launcher` | `boolean` | `true` | Show the floating launcher bottom-right. Set `false` to open the panel from your own control. | | `sounds` | `boolean` | `true` | Play a chime when a reply arrives. | | `locale` | `string` | `navigator.language` | Force a language, any supported BCP 47 tag such as `'ja'` or `'zh-TW'`. | ## `Feddy.identify(options)` ```js Feddy.identify({ userId: 'u_123', email: 'user@example.com', name: 'Ada', avatarUrl: 'https://cdn.example.com/avatars/u_123.png', attributes: { plan: 'pro', credits_left: 42 }, }) ``` Binds the visitor's anonymous contact to a user of your product. Safe to call before `init`; the call is queued and sent once the widget mounts. Does nothing without `userId`. Failures are logged with `console.warn` and never thrown. Details and merge rules are in [Identify users](/docs/guides/identify-users/). | Field | Type | Description | | --- | --- | --- | | `userId` | `string` | Your stable id for the user, 1 to 128 characters. | | `email` | `string` | Optional. Lets replies reach the user by email when your project has a verified sending domain. | | `name` | `string` | Optional. Shown in the inbox. | | `avatarUrl` | `string` | Optional. Absolute `http(s)` URL of a picture you already have of the user, shown in the inbox. Anything else is ignored. | | `attributes` | `object` | Optional. Flat object of strings, numbers, and booleans. Limits are listed in [Identify users](/docs/guides/identify-users/). | ## `Feddy.open()` and `Feddy.close()` ```js document.querySelector('#help').addEventListener('click', () => Feddy.open()) ``` Open or close the panel from your own element. `open()` shows the conversation list. Both do nothing before `init` has mounted the widget. ## `Feddy.onUnreadCountChanged` ```js Feddy.onUnreadCountChanged = (count) => { badge.textContent = count > 0 ? String(count) : '' } ``` Assign a function to receive the unread count whenever it changes. The widget polls every 45 seconds and again each time the tab becomes visible. Use it to drive your own badge when the launcher is off. ## Behavior you do not configure - **Identity**: an anonymous id is stored in `localStorage` under `feddy_anon_id`. Clearing site data resets it, which is why the widget offers to collect an email after the first message when your project has a verified sending domain. - **Language**: the widget ships in English, Simplified Chinese, Traditional Chinese, Japanese, Korean, German, French, Spanish, Brazilian Portuguese, and Russian. `zh-TW`, `zh-HK`, and `zh-MO` resolve to Traditional Chinese; `pt` resolves to Brazilian Portuguese. Other tags fall back to English. Text you write in the dashboard is translated server-side per project settings. - **Attachments**: up to 5 images per message, 10 MB each before resizing, resized to a 2000 px long edge and encoded as WebP in the browser. - **Branding**: name, accent color, and logo come from the dashboard. The accent color is applied as `--fd-accent` inside the shadow root; the widget picks a readable text color for it. - **Launcher**: fixed bottom-right, 56 px, with an unread count badge. Its `z-index` is `2147483000`. --- # Troubleshooting > What to check when Feddy does not behave as expected. Source: https://feddy.app/docs/troubleshooting/ ## Nothing happens when I call `present()` `configure` has not run yet. The console shows `[Feddy] present() called before configure(projectId:)`. Call `configure` in `App.init` or `application(_:didFinishLaunchingWithOptions:)`, before any view can trigger `present()`. ## The web widget does not appear - Check the console for `[Feddy] init requires a projectId`. - Confirm the script tag loaded: `typeof Feddy` should be `"object"`. - If you passed `launcher: false`, nothing is drawn until you call `Feddy.open()`. - `init` is ignored after the first call. Reloading the page is the only way to re-initialize. ## Messages do not arrive in the inbox - Compare the project ID in code with **Settings → Install**. A wrong ID fails silently for the user. - Check you are looking at the right project in the dashboard sidebar. - If you set `apiUrl` or `apiURL` for development, make sure production builds use the default. ## The unread badge does not update - iOS: `.feddyUnreadBadge()` draws only inside `List`, `Form`, or `TabView`. Elsewhere use `FeddyUnreadDot` or observe `FeddyUnread.shared`. - iOS: polling pauses while the app is suspended. Call `Feddy.refresh()` when the scene becomes active. - Web: the count refreshes every 45 seconds and when the tab becomes visible. Assign `Feddy.onUnreadCountChanged` before you expect a callback. - Reading a conversation clears its count. Opening the list alone does not. ## Attributes are missing in the inbox Attributes over the limits are dropped without an error: more than 20 keys, a key over 40 characters, a value over 255 characters, nested objects or arrays, or a total over 8 KB. On iOS, values that are not `String`, `Bool`, a number, or `Date` are dropped on the device. Remember that each `identify` call replaces the whole set. ## Users are never asked for an email The widget asks only when the project has a verified sending domain, and only once per device. Verify a domain under **Settings → Email domain**, or pass `email` in `identify`. ## Replies are not emailed to users All three must hold: the contact has an email, the project's sending domain is verified, and your plan includes email to users. Replies still appear in the app either way. ## The attachment button is missing - iOS: the picker requires iOS 16. On iOS 15 the row is not shown. - Attachments are a paid-plan feature. - Only PNG, JPEG, and WebP are accepted, up to 5 per message. ## The widget is in the wrong language The SDK follows the device or browser language and falls back to English for languages it does not ship. On the web, pass `locale` to `init` to force one. Text you wrote in the dashboard uses your translations when one exists for the user's language, and your default text otherwise. ## The assistant answered something it should not have The assistant only uses your answers and your About text. Find the answer that matched, tighten its keywords or wording, and use **Try it** on the Knowledge page to confirm the change before users see it.