Use base32 encoded UUID to reduce address size
This commit is contained in:
parent
892ec27cc4
commit
a45d136546
4 changed files with 62 additions and 13 deletions
|
|
@ -108,9 +108,9 @@ You'll obtain the best results with a custom [transport rule](https://www.postfi
|
|||
|
||||
```
|
||||
# Transport map - route test emails to happyDeliver LMTP server
|
||||
# Pattern: test-<uuid>@yourdomain.com -> LMTP on localhost:2525
|
||||
# Pattern: test-<base32-uuid>@yourdomain.com -> LMTP on localhost:2525
|
||||
|
||||
/^test-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}@yourdomain\.com$/ lmtp:inet:127.0.0.1:2525
|
||||
/^test-[a-zA-Z2-7-]{26,30}@yourdomain\.com$/ lmtp:inet:127.0.0.1:2525
|
||||
```
|
||||
|
||||
3. Append the created file to `transport_maps` in your `main.cf`:
|
||||
|
|
@ -144,7 +144,7 @@ Response:
|
|||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"email": "test-550e8400@localhost",
|
||||
"email": "test-kfauqaao-ukj2if3n-fgrfkiafaa@localhost",
|
||||
"status": "pending",
|
||||
"message": "Send your test email to the address above"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Transport map - route test emails to happyDeliver LMTP server
|
||||
# Pattern: test-<uuid>@domain.com -> LMTP on localhost:2525
|
||||
# Pattern: test-<base32-uuid>@domain.com -> LMTP on localhost:2525
|
||||
|
||||
/^test-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}@.*$/ lmtp:inet:127.0.0.1:2525
|
||||
/^test-[a-zA-Z2-7-]{26,30}@.*$/ lmtp:inet:127.0.0.1:2525
|
||||
|
|
|
|||
|
|
@ -22,8 +22,10 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
|
@ -50,16 +52,36 @@ func NewAPIHandler(store storage.Storage, cfg *config.Config) *APIHandler {
|
|||
}
|
||||
}
|
||||
|
||||
// uuidToBase32 converts a UUID to a URL-safe Base32 string (without padding)
|
||||
// with hyphens every 7 characters for better readability
|
||||
func uuidToBase32(id uuid.UUID) string {
|
||||
// Use RFC 4648 Base32 encoding (URL-safe)
|
||||
encoded := base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(id[:])
|
||||
// Convert to lowercase for better readability
|
||||
encoded = strings.ToLower(encoded)
|
||||
|
||||
// Insert hyphens every 7 characters
|
||||
var result strings.Builder
|
||||
for i, char := range encoded {
|
||||
if i > 0 && i%7 == 0 {
|
||||
result.WriteRune('-')
|
||||
}
|
||||
result.WriteRune(char)
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
||||
|
||||
// CreateTest creates a new deliverability test
|
||||
// (POST /test)
|
||||
func (h *APIHandler) CreateTest(c *gin.Context) {
|
||||
// Generate a unique test ID (no database record created)
|
||||
testID := uuid.New()
|
||||
|
||||
// Generate test email address
|
||||
// Generate test email address using Base32-encoded UUID
|
||||
email := fmt.Sprintf("%s%s@%s",
|
||||
h.config.Email.TestAddressPrefix,
|
||||
testID.String(),
|
||||
uuidToBase32(testID),
|
||||
h.config.Email.Domain,
|
||||
)
|
||||
|
||||
|
|
@ -94,10 +116,10 @@ func (h *APIHandler) GetTest(c *gin.Context, id openapi_types.UUID) {
|
|||
apiStatus = TestStatusPending
|
||||
}
|
||||
|
||||
// Generate test email address
|
||||
// Generate test email address using Base32-encoded UUID
|
||||
email := fmt.Sprintf("%s%s@%s",
|
||||
h.config.Email.TestAddressPrefix,
|
||||
id.String(),
|
||||
uuidToBase32(id),
|
||||
h.config.Email.Domain,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
package receiver
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -112,8 +113,34 @@ func (r *EmailReceiver) ProcessEmailBytes(rawEmail []byte, recipientEmail string
|
|||
return nil
|
||||
}
|
||||
|
||||
// base32ToUUID converts a URL-safe Base32 string (without padding) to a UUID
|
||||
// Hyphens are ignored during decoding
|
||||
func base32ToUUID(encoded string) (uuid.UUID, error) {
|
||||
// Remove hyphens for decoding
|
||||
encoded = strings.ReplaceAll(encoded, "-", "")
|
||||
|
||||
// Convert to uppercase for Base32 decoding
|
||||
encoded = strings.ToUpper(encoded)
|
||||
|
||||
// Decode from Base32
|
||||
decoded, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(encoded)
|
||||
if err != nil {
|
||||
return uuid.Nil, fmt.Errorf("failed to decode base32: %w", err)
|
||||
}
|
||||
|
||||
// Ensure we have exactly 16 bytes for UUID
|
||||
if len(decoded) != 16 {
|
||||
return uuid.Nil, fmt.Errorf("decoded bytes length is %d, expected 16", len(decoded))
|
||||
}
|
||||
|
||||
// Convert bytes to UUID
|
||||
var id uuid.UUID
|
||||
copy(id[:], decoded)
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// extractTestID extracts the UUID from the test email address
|
||||
// Expected format: test-<uuid>@domain.com
|
||||
// Expected format: test-<base32-uuid>@domain.com
|
||||
func (r *EmailReceiver) extractTestID(email string) (uuid.UUID, error) {
|
||||
// Remove angle brackets if present (e.g., <test-uuid@domain.com>)
|
||||
email = strings.Trim(email, "<>")
|
||||
|
|
@ -133,10 +160,10 @@ func (r *EmailReceiver) extractTestID(email string) (uuid.UUID, error) {
|
|||
|
||||
uuidStr := strings.TrimPrefix(localPart, r.config.Email.TestAddressPrefix)
|
||||
|
||||
// Parse UUID
|
||||
testID, err := uuid.Parse(uuidStr)
|
||||
// Decode Base32 to UUID
|
||||
testID, err := base32ToUUID(uuidStr)
|
||||
if err != nil {
|
||||
return uuid.Nil, fmt.Errorf("invalid UUID in email address: %s", uuidStr)
|
||||
return uuid.Nil, fmt.Errorf("invalid Base32 encoding in email address: %s - %w", uuidStr, err)
|
||||
}
|
||||
|
||||
return testID, nil
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue