docs/03c-quickstart-package
Saturday 28 February 2026

Quick Start: Mau Go Package

This tutorial shows how to build custom Mau applications using the Go package. You’ll create a simple social app from scratch.

Time: ~20 minutes
Prerequisites:

  • Go 1.21+ installed
  • Basic Go knowledge
  • Completed CLI Tutorial (recommended)

Why Use the Package?

The Mau Go package lets you:

  • Build custom social applications
  • Integrate Mau into existing Go projects
  • Create specialized clients (mobile, desktop, web)
  • Automate workflows and bots

Step 1: Set Up Your Project

1mkdir mau-app
2cd mau-app
3go mod init example.com/mau-app
4go get github.com/mau-network/mau@latest

Step 2: Create or Open an Account

Create main.go:

 1package main
 2
 3import (
 4	"fmt"
 5	"log"
 6	"os"
 7	"path/filepath"
 8
 9	"github.com/mau-network/mau"
10)
11
12func main() {
13	// Mau directory path
14	mauDir := filepath.Join(os.Getenv("HOME"), ".mau-app")
15
16	// Check if account exists
17	accountFile := filepath.Join(mauDir, ".mau", "account.pgp")
18	var account *mau.Account
19	var err error
20
21	if _, err := os.Stat(accountFile); os.IsNotExist(err) {
22		// Create new account
23		fmt.Println("Creating new account...")
24		fmt.Print("Name: ")
25		var name string
26		fmt.Scanln(&name)
27		
28		fmt.Print("Email: ")
29		var email string
30		fmt.Scanln(&email)
31		
32		fmt.Print("Passphrase: ")
33		var passphrase string
34		fmt.Scanln(&passphrase)
35
36		account, err = mau.NewAccount(mauDir, name, email, passphrase)
37		if err != nil {
38			log.Fatalf("Failed to create account: %v", err)
39		}
40		fmt.Println("✓ Account created!")
41	} else {
42		// Open existing account
43		fmt.Print("Enter passphrase: ")
44		var passphrase string
45		fmt.Scanln(&passphrase)
46
47		account, err = mau.OpenAccount(mauDir, passphrase)
48		if err != nil {
49			log.Fatalf("Failed to open account: %v", err)
50		}
51		fmt.Println("✓ Account opened!")
52	}
53
54	// Display account info
55	fmt.Printf("\nName:        %s\n", account.Name())
56	fmt.Printf("Email:       %s\n", account.Email())
57	fmt.Printf("Fingerprint: %s\n", account.Fingerprint())
58}

Run it:

1go run main.go

Output:

Creating new account...
Name: Alice
Email: alice@example.com
Passphrase: [your-password]
✓ Account created!

Name:        Alice
Email:       alice@example.com
Fingerprint: 5D000B2F2C040A1675B49D7F0C7CB7DC36999D56

Step 3: Create and Share a Post

Add a function to create posts:

 1package main
 2
 3import (
 4	"bytes"
 5	"encoding/json"
 6	"fmt"
 7	"log"
 8	"os"
 9	"path/filepath"
10	"time"
11
12	"github.com/mau-network/mau"
13)
14
15// Post represents a social media posting
16type Post struct {
17	Context       string    `json:"@context"`
18	Type          string    `json:"@type"`
19	Headline      string    `json:"headline"`
20	ArticleBody   string    `json:"articleBody,omitempty"`
21	Author        Author    `json:"author"`
22	DatePublished time.Time `json:"datePublished"`
23}
24
25type Author struct {
26	Type       string `json:"@type"`
27	Name       string `json:"name"`
28	Identifier string `json:"identifier"`
29}
30
31func createPost(account *mau.Account, headline, body string) error {
32	post := Post{
33		Context:       "https://schema.org",
34		Type:          "SocialMediaPosting",
35		Headline:      headline,
36		ArticleBody:   body,
37		Author: Author{
38			Type:       "Person",
39			Name:       account.Name(),
40			Identifier: account.Fingerprint().String(),
41		},
42		DatePublished: time.Now(),
43	}
44
45	// Marshal to JSON
46	postJSON, err := json.MarshalIndent(post, "", "  ")
47	if err != nil {
48		return fmt.Errorf("failed to marshal post: %w", err)
49	}
50
51	// Create file from JSON
52	reader := bytes.NewReader(postJSON)
53	filename := fmt.Sprintf("post-%d.json", time.Now().Unix())
54
55	// Add file (encrypted for yourself = public post)
56	file, err := account.AddFile(reader, filename, nil)
57	if err != nil {
58		return fmt.Errorf("failed to add file: %w", err)
59	}
60
61	fmt.Printf("✓ Post created: %s\n", file.Name())
62	return nil
63}
64
65func main() {
66	// ... (previous account setup code) ...
67
68	// Create a post
69	fmt.Println("\n--- Create a Post ---")
70	if err := createPost(account, "Hello from Go!", "This post was created programmatically."); err != nil {
71		log.Fatalf("Failed to create post: %v", err)
72	}
73}

Run it:

1go run main.go

Output:

✓ Account opened!
...
--- Create a Post ---
✓ Post created: post-1709107200.json

Step 4: List Your Posts

Add a function to list files:

 1func listPosts(account *mau.Account) error {
 2	// List all your files
 3	files := account.ListFiles(account.Fingerprint(), time.Time{}, 100)
 4
 5	fmt.Printf("\n📝 Your posts (%d):\n\n", len(files))
 6
 7	for _, file := range files {
 8		// Decrypt and parse
 9		content, err := file.Read()
10		if err != nil {
11			log.Printf("Error reading %s: %v", file.Name(), err)
12			continue
13		}
14
15		var post Post
16		if err := json.Unmarshal(content, &post); err != nil {
17			log.Printf("Error parsing %s: %v", file.Name(), err)
18			continue
19		}
20
21		fmt.Printf("🔹 %s\n", post.Headline)
22		if post.ArticleBody != "" {
23			fmt.Printf("   %s\n", post.ArticleBody)
24		}
25		fmt.Printf("   📅 %s\n\n", post.DatePublished.Format("Jan 2, 2006 15:04"))
26	}
27
28	return nil
29}
30
31func main() {
32	// ... (previous code) ...
33
34	// List posts
35	if err := listPosts(account); err != nil {
36		log.Fatalf("Failed to list posts: %v", err)
37	}
38}

Step 5: Add a Friend

Add friend management:

 1func addFriend(account *mau.Account, publicKeyPath string) error {
 2	// Open friend's public key file
 3	file, err := os.Open(publicKeyPath)
 4	if err != nil {
 5		return fmt.Errorf("failed to open key file: %w", err)
 6	}
 7	defer file.Close()
 8
 9	// Add friend
10	friend, err := account.AddFriend(file)
11	if err != nil {
12		return fmt.Errorf("failed to add friend: %w", err)
13	}
14
15	fmt.Printf("✓ Friend added: %s <%s>\n", friend.Name(), friend.Email())
16	fmt.Printf("  Fingerprint: %s\n", friend.Fingerprint())
17
18	return nil
19}
20
21func listFriends(account *mau.Account) error {
22	keyring, err := account.ListFriends()
23	if err != nil {
24		return fmt.Errorf("failed to list friends: %w", err)
25	}
26
27	friends := keyring.All()
28	fmt.Printf("\n👥 Friends (%d):\n\n", len(friends))
29
30	for _, friend := range friends {
31		fmt.Printf("• %s <%s>\n", friend.Name(), friend.Email())
32		fmt.Printf("  Fingerprint: %s\n", friend.Fingerprint())
33		
34		// Check if following
35		follows, _ := account.ListFollows()
36		following := false
37		for _, f := range follows {
38			if f.Fingerprint() == friend.Fingerprint() {
39				following = true
40				break
41			}
42		}
43		
44		if following {
45			fmt.Printf("  Status: Following ✓\n")
46		} else {
47			fmt.Printf("  Status: Not following\n")
48		}
49		fmt.Println()
50	}
51
52	return nil
53}
54
55func main() {
56	// ... (previous code) ...
57
58	// Example: Add a friend from file
59	// friendKeyPath := "/path/to/friend-pubkey.pgp"
60	// if err := addFriend(account, friendKeyPath); err != nil {
61	//     log.Printf("Failed to add friend: %v", err)
62	// }
63
64	// List friends
65	if err := listFriends(account); err != nil {
66		log.Fatalf("Failed to list friends: %v", err)
67	}
68}

Step 6: Follow a Friend and Sync

 1func followFriend(account *mau.Account, fingerprint mau.Fingerprint) error {
 2	// Get friend from keyring
 3	keyring, err := account.ListFriends()
 4	if err != nil {
 5		return fmt.Errorf("failed to list friends: %w", err)
 6	}
 7
 8	friend, err := keyring.Get(fingerprint)
 9	if err != nil {
10		return fmt.Errorf("friend not found: %w", err)
11	}
12
13	// Follow
14	if err := account.Follow(friend); err != nil {
15		return fmt.Errorf("failed to follow: %w", err)
16	}
17
18	fmt.Printf("✓ Now following: %s\n", friend.Name())
19	return nil
20}
21
22func syncFromFriend(account *mau.Account, friendURL string, friendFPR mau.Fingerprint) error {
23	// Create HTTP client for the friend
24	// Note: This is simplified; real implementation needs DNS names from discovery
25	client, err := account.Client(friendFPR, []string{friendURL})
26	if err != nil {
27		return fmt.Errorf("failed to create client: %w", err)
28	}
29
30	// Get last sync time
31	lastSync := account.GetLastSyncTime(friendFPR)
32	fmt.Printf("📥 Syncing from %s (since %v)...\n", friendFPR.String()[:8], lastSync)
33
34	// Sync files
35	if err := client.Sync(lastSync); err != nil {
36		return fmt.Errorf("sync failed: %w", err)
37	}
38
39	fmt.Println("✓ Sync complete!")
40	return nil
41}

Step 7: Start an HTTP Server

 1func startServer(account *mau.Account, port string) error {
 2	// Create server instance
 3	// knownNodes can be empty for local-only operation
 4	server, err := account.Server(nil)
 5	if err != nil {
 6		return fmt.Errorf("failed to create server: %w", err)
 7	}
 8
 9	fmt.Printf("🚀 Starting server on :%s\n", port)
10	fmt.Printf("   Fingerprint: %s\n", account.Fingerprint())
11	
12	// Start server (blocking)
13	if err := server.Listen(":" + port); err != nil {
14		return fmt.Errorf("server error: %w", err)
15	}
16
17	return nil
18}
19
20func main() {
21	// ... (previous code) ...
22
23	// Start server (comment out for non-server mode)
24	// if err := startServer(account, "8080"); err != nil {
25	//     log.Fatalf("Server error: %v", err)
26	// }
27}

Step 8: Build a Simple Feed Reader

Complete example application:

  1package main
  2
  3import (
  4	"encoding/json"
  5	"fmt"
  6	"log"
  7	"os"
  8	"path/filepath"
  9	"time"
 10
 11	"github.com/mau-network/mau"
 12)
 13
 14type FeedItem struct {
 15	Headline      string    `json:"headline"`
 16	Body          string    `json:"articleBody"`
 17	Author        string    `json:"-"`
 18	AuthorFPR     string    `json:"-"`
 19	DatePublished time.Time `json:"datePublished"`
 20	Filename      string    `json:"-"`
 21}
 22
 23func getFeed(account *mau.Account) ([]FeedItem, error) {
 24	var feed []FeedItem
 25
 26	// Get your own posts
 27	myFiles := account.ListFiles(account.Fingerprint(), time.Time{}, 50)
 28	for _, file := range myFiles {
 29		item, err := parseFeedItem(file, account.Name(), account.Fingerprint().String())
 30		if err == nil {
 31			feed = append(feed, item)
 32		}
 33	}
 34
 35	// Get friends' posts
 36	follows, err := account.ListFollows()
 37	if err != nil {
 38		return nil, err
 39	}
 40
 41	for _, friend := range follows {
 42		friendFiles := account.ListFiles(friend.Fingerprint(), time.Time{}, 50)
 43		for _, file := range friendFiles {
 44			item, err := parseFeedItem(file, friend.Name(), friend.Fingerprint().String())
 45			if err == nil {
 46				feed = append(feed, item)
 47			}
 48		}
 49	}
 50
 51	// Sort by date (newest first)
 52	// (Implement sorting logic here)
 53
 54	return feed, nil
 55}
 56
 57func parseFeedItem(file *mau.File, authorName, authorFPR string) (FeedItem, error) {
 58	content, err := file.Read()
 59	if err != nil {
 60		return FeedItem{}, err
 61	}
 62
 63	var post struct {
 64		Type          string    `json:"@type"`
 65		Headline      string    `json:"headline"`
 66		ArticleBody   string    `json:"articleBody"`
 67		DatePublished time.Time `json:"datePublished"`
 68	}
 69
 70	if err := json.Unmarshal(content, &post); err != nil {
 71		return FeedItem{}, err
 72	}
 73
 74	// Only include social media postings
 75	if post.Type != "SocialMediaPosting" {
 76		return FeedItem{}, fmt.Errorf("not a social media posting")
 77	}
 78
 79	return FeedItem{
 80		Headline:      post.Headline,
 81		Body:          post.ArticleBody,
 82		Author:        authorName,
 83		AuthorFPR:     authorFPR,
 84		DatePublished: post.DatePublished,
 85		Filename:      file.Name(),
 86	}, nil
 87}
 88
 89func displayFeed(account *mau.Account) error {
 90	feed, err := getFeed(account)
 91	if err != nil {
 92		return err
 93	}
 94
 95	fmt.Printf("\n📰 Your Feed (%d posts)\n", len(feed))
 96	fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
 97
 98	for _, item := range feed {
 99		fmt.Printf("\n👤 %s (%s...)\n", item.Author, item.AuthorFPR[:8])
100		fmt.Printf("🔹 %s\n", item.Headline)
101		if item.Body != "" {
102			fmt.Printf("   %s\n", item.Body)
103		}
104		fmt.Printf("📅 %s\n", item.DatePublished.Format("Jan 2, 2006 15:04"))
105	}
106
107	fmt.Println("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
108	return nil
109}
110
111func main() {
112	mauDir := filepath.Join(os.Getenv("HOME"), ".mau-app")
113	
114	fmt.Print("Enter passphrase: ")
115	var passphrase string
116	fmt.Scanln(&passphrase)
117
118	account, err := mau.OpenAccount(mauDir, passphrase)
119	if err != nil {
120		log.Fatalf("Failed to open account: %v", err)
121	}
122
123	// Display feed
124	if err := displayFeed(account); err != nil {
125		log.Fatalf("Failed to display feed: %v", err)
126	}
127}

Build and run:

1go build -o mau-reader .
2./mau-reader

Step 9: Send Private Messages

 1type Message struct {
 2	Context   string    `json:"@context"`
 3	Type      string    `json:"@type"`
 4	Text      string    `json:"text"`
 5	Sender    Author    `json:"sender"`
 6	Recipient Author    `json:"recipient"`
 7	DateSent  time.Time `json:"dateSent"`
 8}
 9
10func sendMessage(account *mau.Account, recipientFPR mau.Fingerprint, text string) error {
11	// Get recipient friend
12	keyring, err := account.ListFriends()
13	if err != nil {
14		return err
15	}
16
17	recipient, err := keyring.Get(recipientFPR)
18	if err != nil {
19		return fmt.Errorf("recipient not found: %w", err)
20	}
21
22	msg := Message{
23		Context: "https://schema.org",
24		Type:    "Message",
25		Text:    text,
26		Sender: Author{
27			Type:       "Person",
28			Identifier: account.Fingerprint().String(),
29		},
30		Recipient: Author{
31			Type:       "Person",
32			Identifier: recipientFPR.String(),
33		},
34		DateSent: time.Now(),
35	}
36
37	msgJSON, err := json.Marshal(msg)
38	if err != nil {
39		return err
40	}
41
42	// Create file encrypted only for recipient
43	reader := bytes.NewReader(msgJSON)
44	filename := fmt.Sprintf("msg-to-%s-%d.json", recipientFPR.String()[:8], time.Now().Unix())
45
46	_, err = account.AddFile(reader, filename, []*mau.Friend{recipient})
47	if err != nil {
48		return err
49	}
50
51	fmt.Printf("✓ Message sent to %s\n", recipient.Name())
52	return nil
53}

Step 10: Error Handling Best Practices

 1package main
 2
 3import (
 4	"errors"
 5	"fmt"
 6	"github.com/mau-network/mau"
 7)
 8
 9func robustAccountOpen(mauDir, passphrase string) (*mau.Account, error) {
10	account, err := mau.OpenAccount(mauDir, passphrase)
11	if err != nil {
12		// Check for specific errors
13		if errors.Is(err, mau.ErrIncorrectPassphrase) {
14			return nil, fmt.Errorf("incorrect passphrase, please try again")
15		}
16		if errors.Is(err, mau.ErrNoIdentity) {
17			return nil, fmt.Errorf("no account found at %s, run 'init' first", mauDir)
18		}
19		return nil, fmt.Errorf("failed to open account: %w", err)
20	}
21	return account, nil
22}
23
24func robustAddFriend(account *mau.Account, keyPath string) error {
25	file, err := os.Open(keyPath)
26	if err != nil {
27		return fmt.Errorf("cannot open key file: %w", err)
28	}
29	defer file.Close()
30
31	friend, err := account.AddFriend(file)
32	if err != nil {
33		return fmt.Errorf("failed to import friend key: %w", err)
34	}
35
36	fmt.Printf("✓ Added: %s\n", friend.Name())
37	return nil
38}

Complete Application Structure

Recommended project layout:

mau-app/
├── main.go           # Entry point
├── account.go        # Account management
├── posts.go          # Post creation/reading
├── friends.go        # Friend management
├── sync.go           # Syncing logic
├── server.go         # HTTP server
├── types.go          # JSON-LD type definitions
└── go.mod

What You’ve Learned

✅ Create and open Mau accounts programmatically
✅ Create and share posts using Go structs
✅ List and read encrypted files
✅ Manage friends and follows
✅ Build custom sync logic
✅ Start HTTP servers
✅ Parse Schema.org JSON-LD content
✅ Send private messages
✅ Build complete social applications

Key Package APIs

Function Purpose
mau.NewAccount(dir, name, email, pass) Create new account
mau.OpenAccount(dir, pass) Open existing account
account.AddFile(reader, name, recipients) Create encrypted file
account.GetFile(fpr, name) Retrieve file
account.ListFiles(fpr, after, limit) List files
account.AddFriend(keyReader) Import friend’s key
account.Follow(friend) Start following
account.Client(fpr, addresses) Create sync client
account.Server(peers) Create HTTP server
file.Read() Decrypt and read content

Comparison

Level Best For Complexity
Raw GPG Understanding primitives High
Mau CLI End users, scripts Low
Mau Package Custom apps, integration Medium

Use the package when you need:

  • Custom UI (mobile, desktop, web)
  • Integration with existing systems
  • Automation and bots
  • Specialized social apps

Next Steps


Tip: Check out the test files (*_test.go) in the Mau repository for more usage examples!