Appmint Community is a members' social space on the business's own website. A page (community_page) is one community: a club, a customer group, a brand, a venue or an event. Each page has members, a feed of posts with comments and reactions, stories, announcements, group chats and meetups. Connections, direct messages, follows, bookmarks and badges belong to the person, not to one page.
Members are customers signed in on the site. Everything is keyed by their email. Staff run it from Studio's Community app (/community). The API has two parts:
client/community/…: what members do (community/community-client.controller.ts). Withcall_appenginethese calls act as the signed-in staff person, by their email.community/…: connections, messages, meetings, blocks, plus the staff-only report review (community/community.controller.ts).
Studio's tabs save records with the ordinary repository create and update. The engine reacts to those saves: approvals, role changes, invitations, moderation notices and bans. So repo_create and repo_update do what Studio does. update-partial doesn't: it fires none of those reactions.
Related pages:
- EventOxygen's view of the same community (the event's page, the attendee app): Community management, The feed, Networking.
- Comments and reviews on blog posts and products are a different system: Moderate comments and reviews.
- The site's chat widget is a different system: Live chat on the website.
Ask first
- What is the community, and who is it for? Its name, a one-line description, a cover picture and logo, and its type:
group,brand,topic,venue,organization,personalorother. Event pages make themselves (step 2). - Who can see it? Anyone, including people who aren't signed in (public). Only members, while outsiders see a card and can ask to join (private). Or hidden from everyone who isn't a member (invite only).
- Who can join? Anyone, people approved by an admin, or people invited only.
- Who can post? Anyone signed in, members, or admins only. Are posts held for approval, or held only when someone reports them?
- Who runs it? The email of the owner, and of any admins and moderators. These must be the emails they sign in to the site with.
- Which parts are on: stories, group chat, announcements.
- The address:
/community, or another page name on the site. - Who to invite now, if anyone. Each invitation is an email.
Steps
1. Website: switch on the Community feature
The members' side is a built-in site page (base-app components/community/CommunityRoute.tsx). It sits inside the site's header and footer. It reads ?page=<slug or id> to open one community and ?tab= (feed, members, groups, meetups, messages). Without ?page= it opens the member's busiest community, else the busiest one on the site.
It answers only while the site's community feature is on. With the feature off, /community is a 404 (tested on the local site). To switch it on, add a row to site.data.features:
{ "feature": "community", "enabled": true, "page": "community" }page is optional. It moves the address to /<page>. How to write the features list without losing the site's other settings, and how addresses are claimed: Site page routing. In Studio: Site Config › Features › Community.
Stop: switching it on publishes a page on the live site. Say the address and wait for yes.
Invitation emails link to the default site's host name and the community row's page (else community): https://<host>/community?page=<slug> (community-page.service.ts communityUrl 249–257). Tested: the link was http://phasite-phazzer.localhost:3200/community?page=skilltest-community-runners, which was a 404 because the feature was off. Switch the feature on before anyone is invited.
Hands on: the community's address.
Check: repo_find_one on the site shows the community row. Opening the address shows the community, or "No community pages yet." when there are none.
2. Community › Pages: the page
The way that also makes the owner: call_appengine POST /client/community/pages. The signed-in person becomes the page's owner (a member with role owner), and the slug is made from the title. Tested: a staff sign-in made james@… the owner, and could then post and announce.
{
"title": "Saturday Runners",
"type": "group",
"shortDescription": "Easy 5k every Saturday at 8",
"description": "<p>…</p>",
"coverImage": "https://…/cover.jpg",
"logo": "https://…/logo.png",
"visibility": "private",
"joinPolicy": "approval_required",
"postingPolicy": "members",
"moderationLevel": "flagged_only",
"status": "active",
"features": { "storiesEnabled": true, "chatEnabled": true, "announcementsEnabled": true }
}Send status: "active". This route saves no status by default. A page without a status opens by its link, but the page directory (GET /client/community/pages) lists only active and suspended pages. So it is missing from the site's community switcher and from EventOxygen's Explore. Tested: the page was missing from the directory until its status was set (community-page.service.ts 77–89, 216).
In Studio (Community › Pages › Add Page): Studio's form starts with status: active, visibility: public, joinPolicy: open, postingPolicy: members, moderationLevel: none, and chat off. It makes no owner. Tested: a page saved this way had no members. Add the owner in step 3.
The rules:
| Field | Values | What it does (tested) |
|---|---|---|
title | required | Also the record's name, so it must be unique. A second page with the same title: "A community_page with name "…" already exists." |
slug | made from the title | ?page=<slug> on the site. |
status | active, suspended, archived (Studio), also draft, removed | suspended: everyone can read, but posting, comments and reactions are refused ("This community is paused: posting is closed for now"). archived, removed and draft are 404 to members. |
visibility | public (default), private, invite_only | public: the feed, members and announcements are readable by anyone, even signed out (tested). private: outsiders get only the page's card (title, description, pictures, member count, join policy) and "Page not found" for its feed (tested). invite_only: a 404 for everyone who isn't a member. |
joinPolicy | open (default), approval_required, invite_only | open: joining makes them a member. approval_required: they wait as pending until approved (step 3). invite_only: joining is refused with "An invitation is required". |
postingPolicy | members (default), anyone, admins_only | Refusals: "Join the community to post", "Only page admins post here". Guests can comment and react but not post. Comments and reactions also need membership, unless anyone ("Join the community to take part"). |
moderationLevel | none (default), flagged_only, pre_approve | pre_approve: posts and stories by members wait as pending until a moderator approves them. Owners, admins and moderators skip it. flagged_only: a reported post is held (step 5). |
features | storiesEnabled, chatEnabled, announcementsEnabled, plus feedEnabled and mediaGalleryEnabled | Only an explicit false switches a part off. Stories refused: "Stories are switched off for this community". Group chats refused: "Group chat is switched off for this community" (tested). With announcements off, the site hides them. feedEnabled and mediaGalleryEnabled aren't read anywhere. |
category, tags, images, website | free | Shown on the page. |
linkedEntity, linkedEntityType | Ties the page to an event. Set automatically. |
The page's owner or an admin changes it later with PUT /client/community/pages/<id>. Only these fields are taken: title, slug, type, description, shortDescription, category, tags, status, coverImage, logo, images, visibility, joinPolicy, postingPolicy, moderationLevel, features, website. Anything else is refused: "No supported page fields supplied" (tested). A non-admin is refused with "Page administrator required" (tested). Staff can edit any page in Studio, or with repo_update on community_page.
Every event has its own page. Creating an event makes an event page for it: active, public, open to join, members post (community-page.service.ts 40–60). Each ticket issued adds its holder as a member, with no email sent. Don't make a second page for an event. Find it with repo_find on community_page, { "data.linkedEntity": "<event sk>" }. See Event tickets and check-in.
Hands on: the page's sk and slug.
Check: GET /client/community/pages?limit=50 lists it. Studio's Community › Pages shows it with its visibility and status. On the site, /<community page>?page=<slug> opens it.
3. Community › Pages › Members: owner, staff and members
The Members section is in the page's Edit Page drawer (it shows once the page is saved).
Roles are owner, admin, moderator, member, guest:
| Who | Can |
|---|---|
| owner, admin | edit the page, announce, invite, remove members, approve |
| owner, admin, moderator | post under "admins only", skip pre-approval, open group chats, schedule meetups |
| member | post (by the posting policy), comment, react, join group chats they are added to |
| guest | read, comment and react; not post |
Membership status is active, pending (asked to join), invited, banned or left. Only active counts.
Add the owner or staff to a page made in Studio. repo_create on community_page_member:
{ "page": "<page sk>", "email": "[email protected]", "role": "owner", "status": "active",
"source": "manual", "name": "<page sk>:[email protected]", "joinedAt": "<now>", "notificationPreference": "all" }This sends nothing. The page's member count isn't recounted on a create (tested: it stayed one short). It catches up at the next join, leave or approval.
Make someone an admin or moderator. They must be an active member. On screen: their role picker in the page's Members. By API: repo_update on their member record with { "role": "moderator" }. They get the notice "You're now a moderator of …".
Approve or decline a join request. On screen: Members › Requests, then Approve or Decline. By API, approve with repo_update { "status": "active", "joinedAt": "<now>" }. Tested: they got "You're in! Your request to join … was approved", and the member count went up. Decline deletes the request record.
Invite. On screen: Members › Invite, then pick a customer. By API: POST /client/community/pages/<id>/invite { "email" }, as an owner or admin. Either way the member record is saved as invited, and the person gets the community-invitation email and an in-app notice. Tested: one email, subject "You're invited to join …/community?page=<slug>. They accept by pressing Join on the page, which also works when the page is invite_only (community-page.service.ts 268–292).
Stop before inviting. Each invitation emails a real person. Show the list and wait for yes.
Remove or ban. A member leaves with POST …/pages/<id>/leave. An owner or admin removes someone with the same route. On screen, Ban sets status: banned. A banned person can't rejoin ("Membership is banned"). Restore sets them back to active.
Hands on: who is owner, admin and moderator.
Check: GET /client/community/pages/<id>/members (?role=) lists the active members with name and photo. Studio's Members tabs show Members, Requests, Invited and Banned.
4. Community › Announcements: news for the members
As a page owner or admin: POST /client/community/pages/<id>/announcements. This is published at once. In Studio: Community › Announcements › New Announcement, which starts as Draft.
| Field | Values |
|---|---|
title, content (rich text), media | |
priority | normal (default), important, urgent. Listed pinned first, then by priority, then newest. |
targetAudience | all (default), admins (owners and admins), moderators (owners, admins, moderators) |
targetRoles | Any roles. When set, they decide who sees it, and targetAudience is ignored. |
pinned | The site shows a pinned or urgent one as a banner over the feed. |
expiresAt | Hidden after this time. |
status | draft, published, archived. Members see only published. |
Stop before publishing. Publishing (on create, or a draft saved as published) gives every active member it targets an in-app notice. On a public page, anyone can read it, even signed out. Tested: "Important: Route change — Saturday Runners" reached the member, the author got no notice, and an admins announcement was hidden from a plain member. Show the text and audience, and wait for yes. An announcement is announced once. Later edits don't notify again.
Check: GET /client/community/pages/<id>/announcements as a member lists it.
5. Community › Feed and Moderation: posts and reports
Members post from the site (POST /client/community/posts with page, content, media, contentType text/image/video/link/poll, poll). #tags in the text become hashtags. A post on a page is visibility: page. That means "on this page", not "members only": on a public page it is readable signed out. A page post can't be shared to the global feed ("This post cannot be shared publicly", tested).
Staff actions (Community › Feed, or repo_update on community_post):
| Action | Write | What happens (tested) |
|---|---|---|
| Approve a held post | status: active (was pending) | The author gets "Your post in … was approved and is live". |
| Remove | status: removed | It leaves every feed. The author gets "A moderator removed your post in …". |
| Restore | status: active | Back in the feed. No notice. |
| Pin / unpin | pinned: true/false | Pinned posts lead the first page of the feed. |
| Flag | status: flagged | Hidden from others on a flagged_only or pre_approve page. Still visible on an unmoderated page. |
Reports. A member reports another member with POST /client/community/reports { reportedId, reason, details, context: { postId?, page? } }. reason is spam, harassment, inappropriate, unwanted_contact or other. reportedId is the customer's sk or email. Or they block someone and report in one step: POST /client/community/blocks with report: true. If the report names a post on a flagged_only or pre_approve page, that post is held as flagged at once. Tested: it left other people's feeds, and its author still saw it.
Staff see pending reports in Community › Moderation, or call_appengine GET /community/reports (staff only). The decisions:
| On screen | What is saved | What happens (tested) |
|---|---|---|
| Dismiss | report.status: dismissed, report.action: none | Nothing else. |
| Send Warning | report.status: action_taken, report.action: warning | The member gets "A moderator of |
| Ban Member | report.action: ban. Studio also removes the reported post. | The member is banned on the report's page, else on the reported post's page, else on every page they belong to. They get "You've been removed from |
| Remove Content | report.action: content_removed, the post removed | The post is removed and its author told. |
By API, do it as Studio does. repo_update the report (community_block) with its whole report object ({ ...report, status, action, reviewedAt }) and reportStatus. Then remove the post (repo_update on the post, status: removed) for a ban or content removal. repo_update replaces report whole, so read it first.
POST /community/reports/<id>/review { action: "action_taken" | "dismissed", notes } only marks the report reviewed. It sets no report.action, so it warns nobody, bans nobody and leaves a held post flagged (tested). Use the Studio-style write above.
Stop before a warning, a ban or a removal. Each one tells the member, and a ban also ends their membership. Say who, what they did and which page or pages, and wait for yes.
Hashtags. Community › Hashtags bans a tag (community_hashtag status: banned). Posts with it are then refused: "#skilltest isn't allowed in this community" (tested, any case). It also drops out of trending.
Check: Community › Moderation shows the report as action taken or dismissed. GET /client/community/feed?page=<id> as another member shows the post gone or back.
6. Community › Badges: rewards that award themselves
repo_create on community_badge (or Community › Badges › New Badge):
{ "title": "First Post", "description": "Posted for the first time", "criteria": "Post once",
"category": "engagement", "rarity": "common", "icon": "🏃",
"criteriaType": "posts_count", "criteriaValue": 1, "autoAward": true, "status": "active" }criteriaType that award: posts_count, comments_count, stories_posted, pages_joined, messages_sent, connections_count, meetings_attended, reactions_received (community-badge.service.ts 12–21). A member earns the badge the moment their next action of that kind reaches criteriaValue. They get the notice "You earned the First Post badge — Post once", and earnedBy goes up. Tested. Awards aren't back-dated: a member who already qualified gets it only at their next post, comment, and so on (tested). status: retired hides a badge.
Check: GET /client/community/badges/mine as the member. The site shows earned badges beside the feed.
7. Community › Groups and Meetings: chats and meetups
These are made as page staff, by the owner, an admin or a moderator, through the member routes:
- Group chat:
POST /client/community/groups{ page, title, members: [{ email }], maxMembers?, onlyAdminsCanPost?, allowMediaSharing? }. Every member must be an active page member ("… is not a member of this community"), and the page's chat must be on. Tested. Each message notifies the other group members in-app. - Meetup:
POST /client/community/meetings{ page, title, participantIds: [customer sk or email], startTime, duration (minutes), timezone, location or meetingLink, locationType }. Participants must be page members. A page meetup isconfirmedat once, and the end time is worked out from the duration. Tested: each invitee got "invited you to , and an RSVP told the organizer "… is going to …". Only the organizer or page staff can cancel it, and cancelling notifies everyone., Sat, Nov 7, 8:00 AM CST"
Studio's Groups and Meetings tabs save the record directly. That skips the membership checks and sends no invitation notices.
Stop before scheduling a meetup. Every invitee is notified.
Notices and emails
Community notices are in-app (community_notification): the bell on the site's community page, GET /client/community/notifications, and EventOxygen. Nothing is emailed, texted or pushed, except the invitation. A member's notificationPreference on their membership (all, mentions, none) mutes a page's notices. No route sets it; change it on the member record. People are never told about their own actions.
| Moment | Notice | To |
|---|---|---|
| Someone asks to join | page_join_request | the page's owners and admins |
| Join approved | system: You're in!… | the person |
| Role changed | system: You're now a moderator of… | the person |
| Invited | community-invitation email + page_invite | the person |
| Comment / reply | comment | the post's author / the parent comment's author |
| Reaction on a post | reaction | the post's author |
| Announcement published | announcement | the members it targets |
| Post approved / removed by a moderator | system | the author |
| Report decided: warning / ban | system | the reported member |
| Badge earned | badge_earned | the member |
| Group message | group_message | the other group members |
| Direct message | message | the recipient |
| Connection request / accepted | connection_request / connection_accepted | the other person |
| Meetup invite / RSVP / cancelled | meeting_invite / system | invitees / organizer / everyone on it |
When it's done
Tell the person:
- the address, and what an outsider sees there (everything, a card with Join, or nothing);
- each page's rules in plain words, e.g. "Private, approval to join, members post, reported posts held";
- who is owner, admin and moderator;
- who was invited, and that each got an email;
- any announcements, badges and hashtag bans;
- for reports: the decision and its effect.
Open Studio's Community (/community) on the Pages tab, and the community on the site.
What it can't do yet
- A report also works as a block for the person who made it. Reports are saved as block records, so the reporter stops seeing the reported member's posts and can't message them. The report also shows in their list of blocked people. Tested: a member who reported someone lost that person's posts from their feed, and the blocks list showed the report (
community-block.service.ts166–190,community-post.service.ts181–185,community-messaging.service.ts311–319). - The staff review route doesn't act (step 5):
POST /community/reports/<id>/reviewdoesn't warn, ban or remove. - Pages made through
POST /client/community/pageshave no status, so they are left out of the directory until it is set (step 2). - @mentions do nothing. They are saved as words (
@sam), not emails, and nobody is notified (community-post.service.ts430–433). - People search finds nobody across the community.
GET /client/community/peoplewithoutpageandpeople/suggestionsalways return an empty list. Withpage, it lists the page's members and ignoresq(tested). - Follows don't do anything yet. Following a person, page or hashtag saves a record, but it doesn't change the feed, notify anyone or count followers (
followerCountstays 0, tested). - Badge criteria
events_attendedandcustomare offered in Studio, but nothing awards them. There is no route for staff to award a badge by hand. - Counts drift. A post removed by a moderator, or held as pending, still counts in the page's
postCount. A member record made directly doesn't updatememberCount(step 3). Studio's member count on the Pages list counts every status. - Studio's sidebar shortcuts (Pages, Feed, Moderation…) open the Community app on whatever tab was last open. The app ignores the tab they name (
ui/sidebars/links.ts254–268,community/app.tsx43). Pick the tab inside the app. Read from code, not tested. - The invitation's plain-text version reads "You've been invited you to join…" when there is no inviter name, which happens whenever a customer invites (
default-email-templates.ts525). The HTML version is the one most people see.
Don'ts
- Don't create an event's community page by hand. The event makes it.
- Don't leave a page without
status: "active". - Don't moderate with
update-partial. It skips the approval, removal, ban and warning reactions. Userepo_updateor Studio. - Don't use
POST /community/reports/<id>/reviewto ban or warn. - Don't invite, announce, schedule a meetup, warn or ban without a yes. Each one reaches real people.
- Don't promise email or push notices. Only the invitation is emailed.
- Don't treat a public page as private. Its posts, members and announcements are readable by anyone.