Skip to content

Conversation

@kiwicopple
Copy link
Member

Implements an embedded SMTP server that replaces the Docker-based inbucket container for local email testing. This will make the CLI lighter and faster to start.

Emails are stored as .eml files on disk for easy access.

Features:

  • SMTP server using github.com/emersion/go-smtp
  • File-based storage with configurable path via SUPABASE_MAIL_PATH
  • Email parsing using github.com/jhillyerd/enmime/v2
  • API to list mailboxes, list emails, read and delete emails

Storage format:

$SUPABASE_MAIL_PATH/
└── {recipient-email}/
└── {timestamp}-{id}.eml

Implements an embedded SMTP server that replaces the Docker-based
inbucket container for local email testing. Emails are stored as
.eml files on disk for easy access.

Features:
- SMTP server using github.com/emersion/go-smtp
- File-based storage with configurable path via SUPABASE_MAIL_PATH
- Email parsing using github.com/jhillyerd/enmime/v2
- API to list mailboxes, list emails, read and delete emails

Storage format:
$SUPABASE_MAIL_PATH/
└── {recipient-email}/
    └── {timestamp}-{id}.eml
@kiwicopple kiwicopple requested a review from a team as a code owner January 17, 2026 14:07
@snyk-io
Copy link

snyk-io bot commented Jan 17, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@kiwicopple kiwicopple marked this pull request as draft January 17, 2026 14:07
Documents the embedded mail server package including:
- Configuration options and environment variables
- Usage examples for starting server and reading emails
- Storage format explanation
- API reference
- Comparison with Docker-based solution
Add comprehensive tests covering:
- HTML/multipart email handling
- StoreFromReader method
- Server stop idempotency
- DeleteMailbox via server API
- Raw email content preservation
- Multiple emails sorting in mailbox
…opment

- Add InbucketProvider config option to choose between "embedded" (default)
  and "mailpit" (Docker-based) email servers
- Modify start.go to launch embedded SMTP server when using embedded provider
- Update GoTrue SMTP config to connect via host.docker.internal:2500
- CLI stays running in foreground when embedded mail is active (Ctrl+C to stop)
- Emails stored in .supabase/mail/ or SUPABASE_MAIL_PATH directory
- Docker mailpit container only starts when provider = "mailpit" in config.toml
- Write PID file to .supabase/mail.pid when embedded server starts
- supabase stop reads PID file and sends SIGTERM to gracefully stop the server
- Clean up PID file on shutdown
- Allows stopping embedded mail server from separate terminal via supabase stop
- Add validatePath() to ensure paths stay within storage directory
- Add sanitizeID() to remove dangerous characters from email IDs
- Use filepath.Abs() in NewStorage for consistent path validation
- Apply path validation to ListEmails, GetEmail, DeleteEmail, DeleteMailbox
- Use filepath.Clean() and filepath.Base() to sanitize filenames

Fixes Snyk CWE-23 path traversal warnings.
Comment on lines +98 to +111
// If embedded mail server is running, keep the CLI process alive
// This is needed because the embedded server runs in this process
if embeddedMailServer != nil {
fmt.Fprintln(os.Stderr, "\nEmbedded mail server is running. Press Ctrl+C to stop all services.")
// Wait for context cancellation (Ctrl+C)
<-ctx.Done()
fmt.Fprintln(os.Stderr, "\nStopping embedded mail server...")
StopEmbeddedMailServer()
// Also stop Docker containers
if err := utils.DockerRemoveAll(context.Background(), os.Stderr, utils.Config.ProjectId); err != nil {
fmt.Fprintln(os.Stderr, err)
}
fmt.Fprintln(os.Stderr, "Stopped all services.")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern here is that it relies on cli to be running on foreground. For anyone running local stack in background, we still need to fallback to mailpit container.

For ease of maintenance, I would avoid duplicating the implementation.

You can also measure the overhead of mailpit container by setting [inbucket] enabled = false. I suspect the impact is negligible.

The best outcome IMO is to use docker compose for starting all services. We already import that as a library. Remaining work is move individual DockerStart API calls to a single compose spec.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a good point - i think my main idea was to minimize the surface area. I didn't realize we are using mailpit (because the container name was still inbucket but the docker image is only 13MB so already quite small

I'll close for now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants