Schema

The tables support creates, their fields and types, the indexes it declares, and the rules for changing them, including MongoDB.

Support creates these tables. They are a public contract: a change that breaks reading existing rows is a major release. Rename tables or fields with the schema option. Nothing is added to better-auth’s own tables (user, organization, member). Emails are stored lowercased.

The plugin declares indexes on the fields it filters by: organizationId, userId, assigneeId, mergedIntoId, ticketId, messageId, tag, status, lastMessageAt, requesterEmail, authorEmail, delivery status and scheduledFor, and the message and attachment createdAt.

Which tables you get depends on your options: supportDelivery appears once you set mailer, and supportAttachment once you set attachments. Run the migration again after you turn either on. A generated schema (drizzle or prisma through the better-auth CLI) only has them if the auth config the CLI loads passes mailer and attachments the way production does.

supportTicket

Field Type Meaning
id string Primary key
number number, unique The display number (#123), from supportCounter
subject string
status string open, pending, solved or closed
priority string, optional low, normal, high or urgent
channel string web or email: how it was opened
userId string → user.id, set null, optional The requester’s account
requesterEmail string, optional An account-less requester’s address. Never copied from an account. Cleared when the ticket is linked
requesterName string, optional Same rule as requesterEmail
organizationId string → organization.id, cascade, optional The customer organization. Null for tickets opened by mail
visibility string private, or organization (visible to that organization’s current members)
assigneeId string → user.id, set null, optional The assigned agent
rating string, optional good or bad, set by the requester
verified boolean true for web tickets. For email tickets, the creating message’s DKIM result. A later append never flips it to true. Shown to agents only
mergedIntoId string, optional Set on a ticket merged into another: the target’s id. Indexed, but no foreign key, because better-auth’s drizzle generator can’t type a self-reference under strict. Deleting the target through support nulls it
replyToken string, unique 128-bit random token in the Reply-To address. Never returned by any endpoint or export
requesterSeenAt date, optional When the requester last saw the ticket
agentSeenAt date, optional When an agent last saw it
lastMessageAt date Orders the lists
solvedAt date, optional When it was last solved. The sweep closes it autoCloseAfter later
createdAt, updatedAt date

supportCounter

Operational state for ticket numbers. Left out of exports.

Field Type Meaning
id string Primary key
key string, unique "ticket"
value number The last number handed out

supportTicketTag

One row per tag per ticket. A category is stored here too.

Field Type Meaning
id string Primary key
ticketId string → supportTicket.id, cascade
tag string Lowercase, trimmed, 1 to 50 characters of a-z0-9_-

supportMessage

The thread.

Field Type Meaning
id string Primary key
ticketId string → supportTicket.id, cascade
kind string reply, note (internal, never shown to customers) or event (a timeline entry with data)
authorType string customer, agent or system
userId string → user.id, set null, optional The author’s account
authorEmail, authorName string, optional An account-less sender
body string Plain text. Inbound mail has its quoted text stripped. Empty on events
data json, optional On events: { type: "status" | "assignee" | "priority" | "merged" | "attachment_dropped" | "followup_of", from?, to? }
mentions json, optional string[] of agent user ids, on agent messages
emailMessageId string, unique, optional The RFC 5322 Message-ID of inbound mail, and of outbound mail when the mailer reports it
channel string web or email. On events, what caused them: a status change from an email reply, and attachment_dropped and followup_of rows, are email
verified boolean The inbound DKIM result (false when SES marked the mail as spam). true for in-app messages
createdAt date

supportCannedReply

Staff-wide saved replies.

Field Type Meaning
id string Primary key
title, body string
userId string → user.id, set null, optional The author
createdAt, updatedAt date

supportDelivery

Only with mailer. The outbound queue, one row per mailed reply.

Field Type Meaning
id string Primary key
messageId string → supportMessage.id, cascade The reply it delivers
ticketId string → supportTicket.id, cascade
status string pending, sending, sent, skipped or failed
scheduledFor date When it is due
claimedAt date, optional When a sweep claimed it. A claim older than leaseMs is retried
attempts number Sends tried
detail string, optional The last error, or why it was skipped: seen, note, not_a_reply, no_address or deleted
providerId string, optional The mailer’s id for the send
createdAt date

There is no recipient column. The address is resolved when the mail is sent.

supportAttachment

Only with attachments.

Field Type Meaning
id string Primary key
ticketId string → supportTicket.id, cascade
messageId string → supportMessage.id, cascade, optional Null until attached to a sent message
userId string → user.id, set null, optional The uploader
internal boolean Uploaded by an agent for a note. Never readable by customers
filename string Sanitized: no path parts, up to 200 characters
contentType string
size number Bytes
storageKey string support/<organizationId or "_">/<attachmentId>
createdAt date

Indexes and MongoDB

The plugin declares the indexes listed above. MongoDB’s better-auth adapter creates none of them. Create unique indexes on supportCounter.key, supportTicket.number, supportTicket.replyToken and supportMessage.emailMessageId yourself. Ticket numbering and inbound de-duplication rely on them. Without foreign keys, deleting tickets, organizations and users goes through the cleanup code described in Lifecycle.

Edit this page on GitHub ↗@byo-oss/support 0.3.2