Database Schema

MailPort uses three tables (collections in MongoDB). They are created automatically on first request — no manual migration needed.

Field names are identical across databases; the snake_case names below are the relational form.

senders — senders

Each row is an independent sending channel with its own endpoint, keys, and recipient domain allowlist.

FieldTypeDescription
idTEXT / VARCHAR(64)Primary key, UUID
pidTEXT, uniqueRoute id. A random 5–10 char alphanumeric generated on creation, used to build the send endpoint /api/{pid}/send
nameTEXTDisplay name, dashboard only
typeTEXTDelivery method: smtp (account/password), http (HTTP API forwarding), outlook_oauth2, gmail_oauth2
enabledBOOLEANWhen disabled the endpoint returns 404
hostTEXT, nullableSMTP host. Null when service is set
portINT, nullableSMTP port, usually 465 / 587
secureBOOLEANUse SSL/TLS
serviceTEXT, nullableBuilt-in nodemailer service name, e.g. QQ, Gmail. Overrides host/port. All supported providers
usernameTEXT, nullableSMTP login account; for OAuth types this is the authorized mailbox address
passwordTEXT, nullableSMTP password. AES-256-GCM encrypted, decrypted on read
from_addressTEXT, nullableFrom address; falls back to username
from_nameTEXT, nullableFrom display name
allowed_domainsJSON / JSONBAllowed recipient domains. An empty array means no restriction
http_urlTEXT, nullableRequest URL for the http type
http_methodTEXT, nullableHTTP method for the http type, defaults to POST
http_headersTEXT, nullableCustom headers for the http type, a JSON object string
http_bodyTEXT, nullableCustom body template for the http type, supports {{to}} placeholders
oauth_client_idTEXT, nullableOAuth application Client ID
oauth_client_secretTEXT, nullableOAuth application Client Secret. AES-256-GCM encrypted
oauth_refresh_tokenTEXT, nullableOAuth refresh token. AES-256-GCM encrypted
oauth_authorized_atBIGINT, nullableTimestamp (epoch ms) when OAuth authorization completed; null until then
created_atBIGINTCreated, epoch ms
updated_atBIGINTUpdated, epoch ms

Index: unique index on pid.

Upgrade note: the 8 columns added after v0.1.0 (http_* / oauth_*) are added automatically when the app starts — existing databases need no manual migration.

sender_keys — sender API keys

A sender may hold multiple 32-char keys, each independently enabled, disabled, or revoked.

FieldTypeDescription
idTEXT / VARCHAR(64)Primary key, UUID
sender_idTEXTOwning sender id, cascading delete
keyTEXT32-char random key. AES-256-GCM encrypted, viewable in the dashboard
labelTEXTNote identifying the caller
enabledBOOLEANWhen disabled, requests with this key return 401
last_used_atBIGINT, nullableLast successful call, epoch ms
created_atBIGINTCreated, epoch ms

Index: sender_id.

send_logs — send logs

Every endpoint call is recorded, success or failure.

FieldTypeDescription
idTEXT / VARCHAR(64)Primary key, UUID
sender_idTEXTOwning sender id, cascading delete
key_idTEXT, nullableKey id used for the call
to_addrTEXTRecipient address
subjectTEXTSubject
statusTEXTsuccess or failed
errorTEXT, nullableError message on failure
durationBIGINTElapsed milliseconds
created_atBIGINTTimestamp, epoch ms

Indexes: (sender_id, created_at DESC) compound, status.

Differences by database

AspectPostgreSQLMySQL / TiDBMongoDBSQLite
Array columnJSONBJSONnative arrayJSON string
TimestampsBIGINTBIGINTLongINTEGER
Primary keyTEXTVARCHAR(64)_id (String)TEXT
Cascade deleteFKFKapplication levelFK
Table prefixnonenonenonenone

For MongoDB, deleting a sender also removes its keys and logs in the same operation, since MongoDB has no foreign keys.

Security notes

  • SMTP passwords, OAuth client secrets / refresh tokens, and API keys are stored as v1:-prefixed AES-256-GCM ciphertext.
  • MAILPORT_SECRET is the only decryption path and is never written to the database or logs.
  • The log table never records message bodies or plaintext keys.
  • The health endpoint reports only presence of configuration, never any credentials.