Introduction

This book contains the documentation for all my Matrix bridges. The book is built using mdBook. The source code is available in the mautrix/docs repo.

These docs are mostly targeted towards people who want to run bridges themselves. If you don't want to self-host, some bridges have public instances available (listed on the page of each bridge). Beeper also offers all of these bridges as a service.

Troubleshooting & FAQ

Debugging setup issues should be done in the Matrix rooms for the bridges (linked in the READMEs), rather than in GitHub issues. Additionally, this page will collect some of the most common issues.

Why is the bridge bot not accepting invites? (or not receiving messages from Matrix)

If the bridge starts up successfully, but inviting the bot doesn't work and the logs don't show any errors, it usually means the homeserver isn't sending events to the appservice.

In the case of mautrix-imessage, there's no need to invite the bot for setup, so this issue manifests as outgoing messages not working.

There are a few potential reasons this can happen:

  • There was a misconfiguration in the appservice address/hostname/port config or the homeserver can't reach the appservice for some other reason. The homeserver logs should contain errors in this case (grep for transactions in Synapse's homeserver.log).
    • Note that grepping for transactions will show some data even when it's working correctly. If it shows nothing at all, something is broken.
    • Also note that transaction is not the same as transactions. If you don't include the s at the end, you'll get tons of unrelated logs.
    • For mautrix-imessage, you should also check the wsproxy logs.
    • As of Synapse v1.84 and bridges released after May 2023, the bridges will automatically use the appservice ping mechanism to detect configuration issues and report them in bridge logs.
  • The bridge was down for longer than a few minutes and the homeserver backed off. The homeserver should retry after some time. If it still doesn't work after an hour or so (exact backoff depends on how long the bridge was down), check the homeserver logs.

The bot accepted the invite, but I don't see any responses from the bridge bot in Matrix

Check the bridge logs to see if the bridge is receiving the messages and whether it's responding to them or just not handling them at all.

If there's nothing in the logs, then the homeserver may have backed off on sending transactions as per the above entry.

I don't see any responses from the bridge bot in Matrix, but the responses are visible in the bridge logs

Make sure you didn't ignore the bot.

Settings -> Security & Privacy -> Advanced -> Ignored users on Element.

Why are direct messages showing up under "Rooms" instead of "People"?

All chats in Matrix are actually rooms, and there's no good way for bridges to declare that a room is a DM. Specifically, the DM status is stored in the user's account data in the m.direct event, rather than in the room itself. Since the flag isn't stored in the room, there's no way for the room creator to force the room to be a DM.

The bridges include a hacky workaround, which uses double puppeting to update the m.direct account data event directly. It can be turned on with the sync_direct_chat_list config option. Alternatively, you can manually add individual rooms using the /converttodm command in Element Web/Desktop.

Canonical DMs (MSC2199) should fix the issue permanently by actually storing the DM status in the room itself, but so far it has not been implemented in any clients or servers.

Why do I see the bridge as an unverified session in my device list?

When using old methods for double puppeting, the bridge will have an access token for your account, and therefore show up as a session. However, double puppeting sessions never have encryption keys, which means they can't be verified. Some buggy clients (such as Element) will display non-e2ee-capable devices as "unverified", even though in reality there's nothing to verify.

The new appservice method for double puppeting does not create devices, and will therefore not cause false positives even in buggy clients.

Why are messages showing up as "Encrypted by a deleted session"

All messages sent by the bridge are encrypted using the bridge bot's session even though they're sent with different accounts, which may confuse some clients. The warnings are harmless, so you should just ignore them.

The bridge could technically create a separate e2ee session for each ghost user to avoid the warning, but that would be ridiculously inefficient, so it won't happen. In the future, there may be a proper way to define that the ghost users are delegating e2ee handling to the bridge bot.

Can I verify the bridge e2ee session?

Bridges don't currently support interactive verification nor cross-signing, so you can't verify the bot user using the usual user verification flow. You can use the "Manually verify by text" option to verify the bridge bot's device, but it won't make any of the warnings mentioned above go away, so there isn't really any reason to do it.

The bridge can't decrypt my messages!

It's unfortunately quite easy to misconfigure things in a way that prevents the bridge from decrypting messages. Places to start troubleshooting:

  • Make sure appservice is set to false in the encryption config (unless you're connecting to Beeper servers).
  • Check the logs to ensure the bridge bot syncs are successful. It should log a sync request every 30 seconds on the debug level even if there's no activity, and the status code should always be 200.
    • Synapse has some bugs where /sync starts throwing internal server errors after previously working fine. If that happens, the easiest workaround is to get the latest since/next_batch token (e.g. from your own client, they're global) and insert it into the crypto_account table in the bridge database.
  • Make sure you haven't enabled "Never send encrypted messages to unverified sessions" or similar options in your client. The options usually exist both in global settings and in room settings.
  • Try /discardsession (available on Element Web at least) and see if there are any new kinds of errors in the bridge logs when you next send a message (the client will share a new megolm session, which should show up in the logs).

How do I bridge typing notifications and read receipts?

The bridges use MSC2409 to receive ephemeral events (EDUs) from the Matrix homeserver and bridge them to the remote network. It is enabled by default in new bridge instances, but old ones may need to update the bridge config and/or registration file manually. Additionally, MSC2409 is currently only supported in Synapse.

On the bridge side, the config field is appservice -> ephemeral_events. The registration file given to the homeserver must also have the de.sorunome.msc2409.push_ephemeral field set to true. After MSC2409 is approved, the registration file field will be called push_ephemeral with no prefix. If it's not set, you can either set it manually, or regenerate the registration after setting ephemeral_events to true in the bridge config. Remember to restart the homeserver after modifying the registration file.

Previously there was an option to use /sync with double puppeting to receive ephemeral events without MSC2409 support. However, that option is being phased out, so MSC2409 is the only option now.

Why are contact list names disabled by default?

Some bridges like WhatsApp and Signal have access to the contact list you've uploaded to their servers. However, the bridges will not use those names by default. The reason is that they cause problems if your bridge has multiple Matrix users with the same people in their contact lists.

  • Contact list names might be leaked to other Matrix users using the same bridge.
    • Per-room displaynames could somewhat mitigate this, but even that wouldn't work if you're in some bridged groups with the other Matrix users.
  • Bridge ghosts might flip between names from different Matrix users' contact lists.

If the bridge only has one user, then contact list names should be safe to enable.

pip failed building wheel for python-olm

fatal error: olm/olm.h: no such file or directory

When building with end-to-bridge encryption, you must have a C compiler, python3 dev headers and libolm3 with dev headers installed.

If you want to build without encryption:

  • For Python bridges, don't install the e2be optional dependency.
  • For Go bridges, either build with -tags nocrypto or disable cgo with the CGO_ENABLED=0 env var.

fatal error: olm/pk.h: no such file or directory

libolm2 is too old, you need libolm3.

fatal error: pyconfig.h: no such file or directory

python3-dev is required.

error: command 'gcc' failed: No such file or directory

build-essential is required.

Configuration error: <field> not configured

You didn't change the value of <field> in the config, but that field must be configured for the bridge to work (i.e. the default value will not work). Dots in <field> mean nesting, e.g. bridge.permissions means the permissions field inside the bridge object.

ForeignTablesFound: The database contains foreign tables

As mentioned in the setup page, you should not share a single Postgres database between unrelated programs.

You can create a separate database either using the createdb shell command that is usually included with Postgres, or the CREATE DATABASE SQL statement.

For existing installations, you can use the flag suggested in the error message and hope that there are no table name conflicts in the future. To use the flag in Docker, you'll have to override the startup command, either with a copy of the docker-run.sh script (which can be found in the bridge repo), or just the startup command (python3 -m mautrix_$bridge -c /data/config.yaml --flags).

The as_token was not accepted

This error means you either:

  • didn't add the path to the registration file to the homeserver config,
  • didn't restart the homeserver after adding the path to the config, or
  • modified the tokens somewhere and didn't copy them to the other file

Make sure the tokens match everywhere, that you're looking at the right files, and that everything has been restarted.

The as_token was accepted, but the /register request was not

This error can happen through a few different misconfigurations. Unfortunately the homeserver doesn't tell more specifically. The possible misconfigurations are:

  • having the incorrect value in homeserver -> domain
    • This is the most common issue. The domain must match the server_name in your homeserver's config. If it doesn't, fix it, regenerate the registration file and restart everything.
  • changing the bot username without regenerating the registration
  • having another appservice registration exclusively claiming the same namespace.

Homeserver -> bridge connection is not working

At startup, the bridge will ask the homeserver to check that it can reach the bridge. If the homeserver reports an error, the bridge will log the error and exit. This ensures that it's actually working if the bridge runs successfully, rather than being silently broken by not receiving messages. This mechanism is called "appservice ping".

The error message associated with this log is the error that the homeserver (e.g. Synapse) encountered while connecting to the bridge. It is not an error from the bridge itself.

To fix the error, ensure that your homeserver can reach the bridge at the configured address. The address is configured in the appservice -> address field in the bridge config, although in reality the homeserver reads it from the url field in the registration file that you gave to the homeserver. The bridge config field is just copied to the registration when the registration is generated. If you change the value, you can either manually update the registration, or regenerate it completely.

Why is the latest release old?

When bridges are being actively developed, releases are effectively always outdated. The main branch is generally stable and safe to use in production, although automatic unattended upgrades are not recommended. Bugs should only be reported on the main branch.

New releases for Go bridges happen on the 16th of each month. Sometimes releases may be skipped if there's something blocking the release or if nothing relevant has changed since the last one. Releases outside of the standard cycle only happen if there are severe security issues that must be fixed immediately.

Python bridges do not have a release cycle, releases will happen randomly.

Discord: Your message was not bridged: 50035: Invalid Form Body with relay mode

When using relay mode, the bridge will link directly to your homeserver's media repo for avatars, so it needs to know a public address that Discord's CDN can reach. If you use a local address for the normal homeserver -> address field, you must configure a public address in the public_address field.

Registering appservices

All the bridges here use the Matrix Application Service API. It's mostly the same as the client-server API, but there are a few key differences. Primarily, it means that the bridges

  • can control any user ID in a predefined namespace, e.g. all user IDs starting with @telegram_ and ending with :yourserver.com,
  • don't have rate limits, and
  • have events pushed to them via HTTP, instead of pulling with long polling like normal Matrix clients do.

To get these special privileges, the bridge must be registered on the homeserver as an appservice. In general, this requires root access to the server. Instructions for individual homeserver implementations can be found below.

All of the instructions below require you to have the registration.yaml file ready, so make sure you've reached the point in the bridge setup instructions where it tells you to register the bridge on your homeserver.

The registration file is only necessary for the homeserver. None of the mautrix bridges will try to read it at runtime, as all the relevant information is also in the bridge-specific config file. However, this also means that if you change certain config fields, you must regenerate the registration file (or manually apply the relevant changes). The config fields that affect the registration are:

  • homeserver -> domain
  • appservice -> address
  • appservice -> bot_username (or bot -> username in Go bridges)
  • appservice -> ephemeral_events
  • appservice -> id
  • appservice -> as_token
  • appservice -> hs_token
  • bridge -> username_template
  • bridge -> alias_template (Telegram only)

Synapse

If necessary, copy the registration file somewhere where Synapse can read it. Then add the path to the file under app_service_config_files in Synapse's homeserver.yaml file. The field must be an array of strings, for example:

app_service_config_files:
- /data/mautrix-telegram-registration.yaml

After updating the config, restart Synapse to apply changes. If you change or regenerate the registration file, you will need to restart Synapse every time.

If Synapse fails to start after editing the config, it means you either made a YAML syntax error, or the file path is incorrect or not readable. See the Synapse logs to find out what went wrong exactly.

Some things to keep in mind:

  • When using Docker, the file needs to be mounted inside the container
  • If Synapse is running through systemd, the service file might have security hardening features that block access to certain paths.

Beeper

Follow the instructions at github.com/beeper/bridge-manager.

Dendrite

N.B. Dendrite is not a supported environment, as it often has serious bugs. It is strongly recommended to use Synapse or Conduit instead.

Dendrite works the same way as Synapse, except the relevant config field is config_files under app_service_api (and the config file is usually called dendrite.yaml rather than homeserver.yaml):

app_service_api:
    ...
    config_files:
    - /data/mautrix-telegram-registration.yaml

Conduit

Conduit doesn't use a config file, instead it has an admin command for registering appservices. Go to the admin room (which is created automatically when the first user is registered on the server), copy the contents of the registration YAML file, then send a register_appservice command:

@conduit:your.server.name: register_appservice
```
paste registration.yaml contents here
```

(replacing your.server.name with the server name. You can also use tab autocompletion to mention the @conduit user in most clients).

You can confirm it worked using the list_appservices command (which should show the id field of the just registered appservice):

@conduit:your.server.name: list_appservices

See also: https://gitlab.com/famedly/conduit/-/blob/next/APPSERVICES.md

Construct

  1. Review the following to tweak your registration.yaml:

    • Add the prefix "bridge_" to the as_token. Example: as_token: bridge_1m9jt3xt6qdb4...

    • The bridge id and the sender_localpart have to match. Choose a simple name such as the service being bridged.

    A bridge user and room will be created automatically based on the id (e.g. !id:localhost). It is also okay if these already exist.

  2. Convert the registration.yaml to registration.json using a yaml2json converter such as reserialize or a website.

    apt-get -y install reserialize
    reserialize yaml2json registration.yaml > registration.json
    
  3. Use the console to enter the command:

    bridge set /path/to/registration.json
    

    If the bridge has already been registered before then the prior configuration will be overwritten.

    The registration will take effect immediately without restarting the server or reloading the bridge module.

    The console command bridge will list all bridges by ID. bridge <id> will confirm the configuration for the bridge.

    The following are all functionally equivalent:

    • With terminal access strike ctrl-c and enter bridge set /path/to/registration.json at the prompt.
    • Operator sends message bridge set /path/to/registration.json to the !control room.
    • Operator sends server-side private-command prefix \\control bridge set /path/to/registration.json (any client, any room).
    • Non-interactive registration add the argument -execute "bridge set /path/to/registration.json" when running the server.
    • Non-interactive registration for multiple bridges: use multiple -execute arguments: -execute "bridge set registration1.json" -execute "bridge set registration2.json"

See also: https://github.com/matrix-construct/construct/wiki/Bridges

End-to-bridge encryption

The bridge can optionally encrypt messages between Matrix users and the bridge to hide messages from the homeserver. The use cases for this are:

  • Storing messages encrypted on disk rather than in plaintext.
    • In unencrypted rooms, events are stored in plaintext in the homeserver database.
    • E2BE can be configured to delete keys immediately after encrypting/decrypting, which means the server being compromised in the future won't compromise old bridged messages.
  • Preventing the server from seeing messages at all when bridges are hosted locally:
    • When using Beeper, you can self-host bridges locally and connect them to the Beeper servers. End-to-bridge encryption means the Beeper servers never see messages.
    • If you have your own homeserver on a cloud VPS, you can host bridges on a local raspberry pi or similar to ensure your cloud provider can't see messages.

Basic usage

To enable it, you must install the bridge with dependencies:

  • For Python-based bridges, install the e2be optional dependency.
  • For Go-based bridges, make sure the bridge is built with libolm.
    • CI binaries from mau.dev and release binaries on GitHub are always built with libolm.
  • Docker images for all bridges always support encryption and don't need any special build flags.

After that, simply enable the option in the config (bridgeencryption). If you only set allow: true, the bridge won't enable encryption on its own, but will work in encrypted rooms. If you also set default: true, the bridge will automatically enable encryption in new portals.

If your homeserver is configured to forcibly enable encryption in rooms, you must also set default: true in the bridge config. Force-enabling encryption on the server side will not notify the bridge, so unless the bridge enables encryption by default, the bridge will not find out that encryption was enabled.

You should not set appservice: true at the moment, as the Synapse implementation is still incomplete and has not been tested with the bridges.

Additional security

The bridges contain various additional options to configure how keys are handled. For maximum security, you should set:

  • default: true and require: true to reject any unencrypted messages.
  • All fields except delete_outbound_on_ack under delete_keys to true to ratchet/delete keys immediately when they're no longer needed. This prevents the bridge (and bridge admin) from reading old messages.
  • All fields under verification_levels to cross-signed-tofu. This means only devices with valid cross-signing verification can use the bridge.

Legacy instructions

Legacy registration file workaround

In mautrix-telegram v0.8.0 release candidates, you had to manually apply a workaround for MSC2190. In newer versions (mautrix-telegram v0.8.0+, mautrix-python v0.5.0-rc3+) the workaround is applied automatically to all newly generated registration files. For old registration files, you can either regenerate the file or apply the workaround manually:

  1. Change sender_localpart in the registration to something else. Any random string will do.
  2. Add a new entry in the users array for the bridge bot (the previous value of sender_localpart). If you used the default telegrambot, the result should look something like this:
    namespaces:
        users:
        - exclusive: true
          regex: '@telegram_.+:your.homeserver'
        - exclusive: true
          regex: '@telegrambot:your.homeserver'
    
  3. Using the as_token, make a call to register the bot user. It's fine if this says the user is already in use. This step only applies to new bridges, but new bridges don't need to do this workaround.
    $ curl -H "Authorization: Bearer <as_token>" -d '{"username": "telegrambot"}' -X POST https://your.homeserver/_matrix/client/r0/register?kind=user
    

Double puppeting

You can replace the Matrix ghost of your remote account with your Matrix account. When you do so, messages that you send from other clients will be sent from your Matrix account instead of the default ghost user. In most of the bridges, this is necessary to bridge DMs you send from other clients to Matrix.

Benefits of double puppeting:

  • Automatically accept invites to new chats.
  • Bridge messages you send from the native app in direct chats.
  • Bridge messages you send from the native app as your Matrix real user instead of the bridge's ghost user.
  • Optionally sync some details like low priority, favorites, mute status and direct chat status.

Automatically

Instead of requiring everyone to manually enable double puppeting, you can give the bridge access to log in on its own. This makes the process much smoother for users, and removes problems if the access token getting invalidated, as the bridge can simply automatically relogin.

This method requires administrator access to the homeserver, so it can't be used if your account is on someone elses server (e.g. using self-hosted bridges from matrix.org). In such cases, manual login is the only option.

Appservice method (new)

N.B. This method is not supported in the legacy (Python) Signal bridge nor the current iMessage and Slack bridges. You can use the alternative methods like shared secret login documented below for those.

This method doesn't log in at all, instead it uses an as_token directly with the user_id query parameter. It should work on all homeserver implementations that support appservices. However, some servers don't follow the spec, and may not work with a null url field. This method also makes timestamp massaging work correctly and disables ratelimiting for double puppeted messages.

Since there's no login step, this method also has the benefit of not adding confusing sessions to the session list visible to the user.

  1. First create a new appservice registration file. Don't touch the bridge's main registration file, and make sure the ID and as/hs tokens are different (having multiple appservices with the same ID or as_token isn't allowed).

    # The ID doesn't really matter, put whatever you want.
    id: doublepuppet
    # The URL is intentionally left empty (null), as the homeserver shouldn't
    # push events anywhere for this extra appservice. If you use a
    # non-spec-compliant server, you may need to put some fake URL here.
    url:
    # Generate random strings for these three fields. Only the as_token really
    # matters, hs_token is never used because there's no url, and the default
    # user (sender_localpart) is never used either.
    as_token: random string
    hs_token: random string
    sender_localpart: random string
    # Bridges don't like ratelimiting. This should only apply when using the
    # as_token, normal user tokens will still be ratelimited.
    rate_limited: false
    namespaces:
      users:
      # Replace your\.domain with your server name (escape dots for regex)
      - regex: '@.*:your\.domain'
        # This must be false so the appservice doesn't take over all users completely.
        exclusive: false
    
  2. Install the new registration file the usual way (see Registering appservices).

  3. Finally set as_token:$TOKEN as the secret in login_shared_secret_map (e.g. if you have as_token: meow in the registration, set as_token:meow in the bridge config).

    bridge:
      ...
      login_shared_secret_map:
        your.domain: "as_token:meow"
      ...
    

If you set up double puppeting for multiple bridges, you can safely reuse the same registration by just setting the same token in the config of each bridge (i.e. no need to create a new double puppeting registration for each bridge).

This method works for other homeservers too, you just have to create a new registration file for each server, add the token to login_shared_secret_map, and also add the server address to double_puppet_server_map (for the bridge server, adding to the server map is not necessary as it defaults to using the one configured in homeserver -> address).

Shared secret method (legacy, synapse-only)

  1. Set up matrix-synapse-shared-secret-auth on your Synapse.
    • Make sure you set m_login_password_support_enabled to true in the config.
    • You should also set com_devture_shared_secret_auth_support_enabled to false as having that option enabled breaks user-interactive auth in some clients (e.g. you won't be able to sign out other devices or reset cross-signing in Element).
  2. Add the login shared secret to bridgelogin_shared_secret_map in the config file under the correct server name.
    • In mautrix-imessage and in past versions of other bridges, the field is called login_shared_secret, as double puppeting was only supported for local users.
  3. The bridge will now automatically enable double puppeting for all users on servers with a shared secret set when they log into the bridge.

Appservice method (legacy, deprecated)

This method is not recommended. Doing this causes all events from rooms your user is in to be pushed to the bridge, which then makes the bridge bot join the rooms (as the bridge assumes it only receives events meant for it).

Additionally, it only works for users who are on the same homeserver as the bridge, it can't be used with other homeservers at all (even with admin access).

  1. Modify the registration file to add a user namespace covering all users in addition to the bridge_.+ and bridgebot regexes. Make sure you set exclusive: false for the new regex.

    namespaces:
      users:
      - ...existing regexes...
      - regex: '@.*:your\.domain'
        exclusive: false
    

    Restart the homeserver after modifying the registration.

  2. Set the shared secret in the bridge config to appservice:

    bridge:
      ...
      login_shared_secret_map:
        your.domain: appservice
      ...
    
  3. The bridge will now use appservice login enable double puppeting for all local users when they log into the bridge.

Manually

Double puppeting can only be enabled after logging into the bridge. As with the normal login, you must do this in a private chat with the bridge bot.

N.B. This method is not currently supported in mautrix-imessage and mautrix-slack.

  1. Log in on the homeserver to get an access token, for example with the command
    $ curl -XPOST -d '{"type":"m.login.password","identifier":{"type": "m.id.user", "user": "example"},"password":"wordpass","initial_device_display_name":"a fancy bridge"}' https://example.com/_matrix/client/v3/login
    
    You may want to change the initial_device_display_name field to something more descriptive, or rename it from another client after logging in.
    • In the past, getting a token from an existing client like Element was the recommended easy way. However, multiple clients using the same token can cause issues with encryption, so doing that is no longer allowed.
  2. Send login-matrix <access token> to the bridge bot. For the Telegram bridge, send login-matrix without the access token, then send the access token in a separate message.
  3. After logging in, the default Matrix ghost of your remote account should leave rooms and your account should join all rooms the ghost was in automatically.

Manually with SSO

If you only have SSO login on your homeserver, the above example with password login won't work. However, doing SSO login manually is still possible, just a bit more work.

  1. Open https://example.com/_matrix/client/v3/login/sso/redirect?redirectUrl=http://localhost:12345 in a browser. The redirect URL at the end doesn't have to be a real server, since you can just copy the relevant value in the browser URL bar after the redirect.
  2. Go through the SSO process, then once it redirects to localhost:12345, copy the value of the loginToken query parameter.
  3. Log into the homeserver with the login token:
    $ curl -XPOST -d '{"type":"m.login.token","token":"THE TOKEN","initial_device_display_name":"a fancy bridge"}' https://example.com/_matrix/client/v3/login
    
  4. Follow steps 2 and 3 of the normal password login instructions.

Backfilling messages

Most bridges here support fetching old messages and backfilling them into the Matrix room. However, the level of support and config options vary a lot between bridges.

In general, backfill happens automatically, and the recommended way to do backfilling is to configure the bridge the way you want before starting to use it. Some bridges also have a backfill command, but automatic backfill is better due to the Matrix limitations mentioned in the section below.

How backfilling works

Bridges can set the timestamp of each message they send, which is the basic principle behind backfilling. This mechanism is called timestamp massaging.

Matrix doesn't give bridges any way to actually insert messages into the room history, which means backfilled messages always appear at the "end" of the room, even if their timestamps say they're older. In other words, historical message backfill only works in new empty rooms, because backfilling older messages would cause them to be in the wrong order. Messages that were missed while the bridge was offline can also be backfilled in existing rooms, but that behavior usually doesn't need configuring, so most of this page talks specifically about backfilling historical messages.

MSC2716 used to be a method for inserting messages into room history, but the original goal of migrating Gitter history to Matrix ended up being done without actual backfill just using timestamp massaging, so the Element/Synapse developers working on MSC2716 decided to abandon it. Beeper (who develops these bridges) also doesn't use Synapse for bridged rooms, which means there's nobody left to work on MSC2716. Support for MSC2716 has been removed from Synapse and the bridges. Some bridges may still have config options mentioning MSC2716, but those will be replaced with Beeper-specific options in the future.

A future spec proposal may enable true backfilling again, but currently it seems unlikely that anyone would want to spend time to figure out the federation-related issues that MSC2716 encountered, as well as making the API easy to use for bridges.

Quirks of backfill in different bridges

WhatsApp

WhatsApp primarily uses "history sync" blobs, which the phone automatically sends to linked devices soon after successfully linking. If backfill is enabled, the bridge will temporarily save those messages to the database, and delete them once it has either backfilled the chat or found that the room already has messages and backfill isn't possible anymore. There's no way to re-request the initial history sync blobs, so if backfill is disabled or something goes wrong, the only way to retry is to log out and back in.

Additionally, the WhatsApp bridge currently only supports backfilling in newly created rooms. If you log out and log back in, it does not backfill missed messages in existing portals even if it's technically possible (i.e. when there haven't been any new messages).

If max_initial_conversations is set to zero or higher, messages in chats without portal rooms will be stored in the bridge database until the room is created for some other reason (like a new incoming message), at which point backfill will happen and the messages will be deleted. The field defaults to -1 (= create all chats), which means messages won't be stored for long with the default configuration.

The amount of history sent by the phone depends on what the linked device requests: web clients request 3 months, while desktop clients request 1 year. The amount requested by the bridge can be configured using the request_full_sync and full_sync_config config options. Note that the full sync fields do not affect how much is actually backfilled: if you want more messages, you must also change the message_count option.

More recently, WhatsApp has also added on-demand history syncs, but those are not yet implemented in the bridge. On-demand history sync wouldn't be particularly useful in most cases, as messages can't be inserted into the history anyway. It would primarily be useful if something goes wrong in the initial backfill, or when receiving a message in a very old chat that wasn't included in the initial history sync blob.

Signal

Native Signal clients don't support any sort of history transfer (yes, the UX of their official desktop app is horrible). Eventually, the bridge may add an option to import backup files from Signal Android, but that is not supported yet.

Slack

The Slack bridge does not support traditional backfill currently, it only supports the Beeper flavor of MSC2716.

Initial bridge config

These bridges contain quite a lot of configuration options. This page is meant to help with finding the important ones that must be changed when setting up a bridge, as well as highlight some options that are usually useful.

Mandatory fields

When setting up a bridge, some fields have default example values, and must be changed to match your setup. The bridge simply won't work without them, and if you forget any of them, the bridge won't start up successfully and will print errors in logs.

  • homeserver -> address - the address that the bridge can use to connect to your homeserver (like Synapse or Conduit). This should usually be a localhost or internal docker network address (like http://localhost:8008 or http://synapse:8008).
    • If you use workers, then it should probably point at your reverse proxy, but you can still configure your reverse proxy to expose a local non-TLS interface with the same routing rules as the public TLS interface.
  • homeserver -> domain - the domain of the homeserver. This is always the same as server_name in the homeserver config, it is not affected by any reverse proxies, docker networking or other such things.
  • If you're using Docker, you'll have to change appservice -> address too. That address is what the homeserver will use to connect to the bridge. localhost won't work in Docker, but container names do (e.g. http://mautrixbridge:29318), as long as the bridge and homeserver are in the same Docker network.
    • Note that the address field only affects the generated registration file. The host/port where the bridge listens are the two fields below address, but those usually don't need to be changed.
  • The database config has an example postgres URI, which obviously won't work. You can either point it at a real Postgres database, or change it to use SQLite. Postgres is recommended if you have multiple users, but SQLite is fine for small instances.
    • Never point the bridge at another program's (like synapse's) database. When using Postgres, you can create a new database in an existing Postgres instance.
  • Way below in bridge -> permissions, you'll have to change the examples to match your server.

Bridge-specific mandatory fields

mautrix-telegram

The Telegram bridge requires that you create an "app" at https://my.telegram.org/apps and provide the api_id and api_hash in the config. API keys don't grant access to any Telegram account, they're just required to connect to the API in the first place. Login happens afterwards using bridge commands.

mautrix-meta

The Meta bridge is effectively two bridges in one codebase: Facebook Messenger and Instagram DMs. However, it can't handle both types of accounts with one instance. Instead it has a config option (meta -> mode) to choose which service to connect to.

When changing the service, you'll also need to change a bunch of other fields, or otherwise it'll look like an Instagram bot connecting to Messenger. The fields to change listed below. For most fields, you can just replace "instagram" with "facebook".

List of fields
  • meta -> mode (duh)
  • appservice -> id
  • appservice -> bot -> username
  • appservice -> bot -> displayname
  • appservice -> bot -> avatar
    • Instagram: mxc://maunium.net/JxjlbZUlCPULEeHZSwleUXQv
    • Messenger: mxc://maunium.net/ygtkteZsXnGJLJHRchUwYWak
  • bridge -> username_template
  • bridge -> management_room_text -> welcome

If you're duplicating an already-working bridge config, also remember to change the appservice port and database URI (you can't share one database for two bridges). The as_token and hs_token will be regenerated when you generate a new registration.

Other useful things

  • Setting up double puppeting is strongly recommended. The relevant config field for automatic double puppeting is bridge -> login_shared_secret_map.
    • If you only set it up for your own server, you don't need to touch double_puppet_server_map, that's only for remote servers.
    • For the bridges that sync favorite/pin/archive/mute status, you can enable that part with the pinned_tag/archive_tag/mute_bridging config options.
  • End-to-bridge encryption works best if set up before logging into the bridge, because there's currently no proper way to enable encryption in existing rooms.
  • Most bridges have some automatic portal creation and message backfill, so checking the backfill or history_sync config section under bridge is recommended. Backfilling after creating rooms is not possible, so it has to be configured to your liking before logging in.
    • Related to backfill, most bridges also have options to specify how many chats to backfill initially. Those options may be under backfill config, or separately inside the bridge section.

Relay mode

Some of the bridges here support relaying messages for unauthenticated users through the account that another Matrix user is logged in as.

  1. Enable relay mode by setting bridgerelayenabled to true in the bridge config. Also make sure that the users you want to invite have at least the relay level in the permissions section.
  2. Log into the bridge normally using the relaybot account.
    • If you want a separate remote account for the relaybot while using your own account for your own Matrix user, you should make a dedicated Matrix account for the relaybot. If you do this, make sure to run the next command using the new Matrix account too.
    • Using the same dedicated account for multiple bridges is fine, so you can have a server-wide "relay" account that acts as the relay for all the bridges.
  3. Run !prefix set-relay in the chats where you want to use the relaybot. (replace !prefix with the appropriate command prefix for the bridge, like !signal or !wa)
  4. Use !prefix set-pl 100 to be able to modify room settings and invite others.

If you want to bridge existing rooms, you'll have to manually update the mxid column in the portal table to point to the room you want bridged.

Note that reactions from relayed users will not be bridged at all, because the bot wouldn't be able to bridge sender info nor multiple reactions of the same emoji.

Support table

Minimum bridge versions that support the relay system documented above.

BridgeVersion
TelegramDifferent system
WhatsApp0.2.0
Signal0.2.0
Instagram0.1.2
Facebook0.3.3
Meta0.1.0
Google Chatnot yet supported
Twitternot yet supported
Google Messagesnot yet supported
iMessage†0.1.0/3df789e2
DiscordDifferent system
Slacknot yet supported (will likely use different system)

† iMessage doesn't require set-relay, relay mode is enabled in all chats automatically if enabled in the config. The permissions section is replaced with relay -> whitelist.

Bridge setup with Docker

Please select a bridge above. If you don't select a bridge, you'll have to manually replace occurrences of $bridge with the correct name before running commands.

This page contains instructions for setting up the bridge in Docker. To set up the bridge outside of Docker, see the language-specific instructions: Python, Go (to find out which bridge language the bridge you want is written in, check the sidebar to see which section it's under).

If you need help with setting up the bridge, you can ask in the Matrix room: #$bridge:maunium.net. For help with setting up other parts like the homeserver that aren't the bridge, refer to their documentation to find support rooms.

Requirements

  • Docker
  • A Matrix homeserver that supports application services (e.g. Synapse) You need access to register an appservice, which usually involves editing the homeserver config file.
  • mautrix-whatsapp: A WhatsApp client running on a phone or in an emulated Android VM.
  • mautrix-signal: A Signal client that can add linked devices (both official mobile apps and some unofficial clients like signal-cli work).

Setup

Docker images are hosted on dock.mau.dev. Available docker tags are generally :latest, :git tag, :git commit-amd64 and :git commit-arm64. The latest and git tag specific docker tags are manifests that contain both amd64 and arm64 images.

  1. Create a directory for the bridge and cd into it: mkdir mautrix-$bridge && cd mautrix-$bridge.
    N.B. The docker image will chown its /data directory to UID 1337. The commands below mount the working directory as /data, so make sure you always run them in the correct directory.
  2. Pull the docker image with docker pull dock.mau.dev/mautrix/$bridge:<version>. Replace <version> with the version you want to run (e.g. latest or v0.6.0).
  3. Run the container for the first time, so it can create a config file for you:
    docker run --rm -v `pwd`:/data:z dock.mau.dev/mautrix/$bridge:<version>
    
  4. Update the config to your liking. See the initial bridge config page for recommendations.
    • Keep in mind that localhost is not the correct address inside Docker (unless using network=host mode). Usually you should have the bridge and homeserver in the same Docker network, and use the container names as addresses (e.g. http://mautrix-$bridge:$bridgeport and http://synapse:8008).
    • Make sure you don't share databases between unrelated programs. Shared postgres instance is fine, but shared database is not.
  5. Generate the appservice registration by running the container again, same command as above.
  6. Register the bridge on your homeserver (see Registering appservices).
  7. Run the bridge:
    docker run --restart unless-stopped -v `pwd`:/data:z dock.mau.dev/mautrix/$bridge:<version>
    
    Additionally, you should either add the bridge to the same Docker network as Synapse with --network=synapsenet (when both are running in Docker), or expose the correct port with -p $bridgeport:$bridgeport (when the homeserver is outside Docker).

Upgrading

  1. Pull the new version (setup step 1)
  2. Start the new version (setup step 6)

Docker compose

  1. Create a directory for the bridge like step #0 in the Docker CLI instructions above.
  2. Create docker-compose.yml that contains something like this:
    version: "3.7"
    
    services:
      mautrix-$bridge:
        container_name: mautrix-$bridge
        image: dock.mau.dev/mautrix/$bridge:<version>
        restart: unless-stopped
        volumes:
        - .:/data
    
        # If you put the service above in the same docker-compose as the homeserver,
        # ignore the parts below. Otherwise, see below for configuring networking.
    
        # If synapse is running outside of docker, you'll need to expose the port.
        # Note that in most cases you should either run everything inside docker
        # or everything outside docker, rather than mixing docker things with
        # non-docker things.
        #ports:
        #- "$bridgeport:$bridgeport"
        # You'll also probably want this so the bridge can reach Synapse directly
        # using something like `http://host.docker.internal:8008` as the address:
        #extra_hosts:
        #- "host.docker.internal:host-gateway"
    
        # If synapse is in a different network, then add this container to that network.
        #networks:
        #- synapsenet
    # This is also a part of the networks thing above
    #networks:
    #  synapsenet:
    #    external:
    #      name: synapsenet
    
    
  3. Follow the rest of the Docker setup, but use compose commands instead of the raw docker commands: docker-compose up -d to start, docker-compose stop to stop and docker-compose pull to update.

If you want to set it up in an existing docker-compose file instead of a new dedicated one, simply adjust the volumes section to mount a subdirectory instead of the current directory as the data directory:

volumes:
- ./mautrix-$bridge:/data

When you put the bridge and Synapse in the same docker-compose file, networking should work out of the box, which means you don't need any of the commented ports or networks things in the example compose file.

Kubernetes

Kubernetes setups aren't officially supported. However, there are some things you should note if you want to run the bridges in k8s or similar systems:

  • You can bypass the startup script and just run the main bridge command directly to avoid permission mangling, automatic registration generation, and other such things. The bridge also doesn't need the registration file at all when doing this, only the config file is needed.
    • You can also add --no-update to the command to tell the bridge to not try to write the config to disk.
  • A StatefulSet is likely the best way to run the bridge, because only one container can be running at a time. Multiple containers even briefly are extremely unsupported and may cause your server to explode.
  • You should set publishNotReadyAddresses: true. The bridges will check homeserver -> bridge connectivity on startup, which may fail or be delayed if k8s delays publishing the bridge address. There is also no reason to not publish addresses ASAP, because you can only have one instance of the bridge running at a time.
  • Liveness and readiness endpoints are available at /_matrix/mau/live and /_matrix/mau/ready respectively, but they aren't particularly useful due to the points mentioned above.

Contributing guidelines

Development instructions

The latest version of Go is recommended for developing Go bridges, but using the previous version is fine too. Go only supports the last two releases, so anything older than that is EOL and will not work.

Since the bridges use cgo, you'll also need a C compiler and libolm-dev installed. For Signal, you can either install Rust, or just download libsignal_ffi.a from the CI to avoid the Rust dependency (see normal Signal setup instructions for that).

You should install pre-commit and run pre-commit install in the repo before committing to have linters run automatically. You can also run pre-commit run -a before pushing to lint everything. Finally, you can let GitHub actions run the linters for you after pushing, but it's usually more effort to go back and fix things at that point.

Using Linux or macOS is recommended for development. Windows is generally not a supported environment for anything, so WSL is likely necessary if you're running Windows.

A local Synapse instance is useful for testing bridges. Alternatively, you can sign up for Beeper and use bridge-manager to run local bridges against the Beeper servers (there's a convenient --local-dev flag for bbctl run).

Code style

See https://beeper.notion.site/Beeper-Go-Guidelines-ae943532d96f4ad6a614baf836c073eb

Making pull requests

Updating the changelog file is not necessary when making a pull request. That will be done separately when a release is being made.

If you're planning on making bigger changes, you should join the Matrix room linked in the project readme and briefly explain your plan to confirm that it makes sense and is wanted.

Pull requests may be missed and/or forgotten for extended periods of time, especially if they involve more complicated changes. Feel free to remind us in the appropriate Matrix room if it seems like nothing is happening. Pinging on GitHub is not recommended.

Python bridge setup

Please select a bridge above. If you don't select a bridge, you'll have to manually replace occurrences of $bridge with the correct name before running commands.

This page contains instructions for setting up the bridge in a virtualenv. You may also want to look at other ways to run the bridge:

Please note that everything in these docs are meant for server admins who want to self-host the bridge. If you're just looking to use the bridges, check out Beeper, which provides fully managed instances of all of these bridges.

If you need help with setting up the bridge, you can ask in the Matrix room: #$bridge:maunium.net. For help with setting up other parts like the homeserver that aren't the bridge, refer to their documentation to find support rooms.

Requirements

  • Python 3.10 or higher with pip and virtualenv.
  • A Matrix homeserver that supports application services (e.g. Synapse). You need access to register an appservice, which usually involves editing the homeserver config file.
  • A PostgreSQL server, v10 or higher (which you should already have for Synapse).
    • Make sure you don't share databases between unrelated programs. Shared postgres instance is fine, but shared database is not.
  • If installing optional dependencies, see the optional dependencies page.
  • mautrix-telegram: Telegram app ID and hash (get from my.telegram.org).
  • mautrix-telegram: LottieConverter if you want animated stickers to be converted to something viewable on Matrix.
  • Bridges with voice messages: ffmpeg to transcode audio files (just install it with your system package manager).
  • mautrix-signal (legacy): An instance of signald.

Production setup

Don't use sudo for any of these steps (and preferably don't use the root user either).

  1. Create a directory for the bridge. Do not clone the repository.
  2. Set up a virtual environment.
    1. Create with virtualenv -p /usr/bin/python3 . (note the dot at the end)
      • You should not use a subdirectory for the virtualenv in this production setup. The pip install step places some required files at the root of the environment.
    2. Activate with source ./bin/activate
  3. Install the bridge with pip install --upgrade mautrix-$bridge[all]
    • [all] at the end will install all optional dependencies. This includes end-to-bridge encryption, which requires libolm3. See the optional dependencies page for more info.
    • If you want the master branch instead of a release, use pip install --upgrade mautrix-$bridge[all]@git+https://github.com/mautrix/$bridge.git.
  4. Copy example-config.yaml to config.yaml.
  5. Update the config to your liking. See the initial bridge config page for recommendations.
  6. Generate the appservice registration with python -m mautrix_$bridge -g. You can use the -c and -r flags to change the location of the config and registration files. They default to config.yaml and registration.yaml respectively.
  7. Register the bridge on your homeserver (see Registering appservices).
  8. Run the bridge python -m mautrix_$bridge.

Upgrading (production setup)

  1. Make sure you're in the virtualenv (source ./bin/activate).
    • Note: if you updated Python, you'll have to recreate the virtualenv to apply the update.
  2. Run the bridge install command again (install step #2).

Development setup

  1. Clone the repository.
  2. Optional, but strongly recommended: Set up a virtual environment.
    1. Create with virtualenv -p /usr/bin/python3 .venv
    2. Activate with source .venv/bin/activate
  3. Install dependencies with pip install --upgrade -r requirements.txt
    • Optionally, add -r optional-requirements.txt to install optional dependencies. Some of the optional dependencies may need additional native packages. See the optional dependencies page for more info.
  4. Continue from step #3 of production setup.
  5. For linting: pip install -r dev-requirements.txt to install Black, isort and pre-commit, then install the Git hook with pre-commit install. This will ensure that code is properly formatted when you commit, to avoid having to fix linting errors when the CI complains.

Upgrading (development setup)

  1. Make sure you're in the virtualenv (source .venv/bin/activate).
    • Note: if you updated Python, you'll have to recreate the virtualenv to apply the update.
  2. Pull changes from Git.
  3. Run the dependency install command again (install step #2).

systemd service

  1. Create a user for the bridge:
    $ sudo adduser --system mautrix-$bridge --home /opt/mautrix-$bridge
    
  2. Follow the production setup instructions above. Make sure you use that user and home directory for the bridge.
  3. Create a systemd service file at /etc/systemd/system/mautrix-$bridge.service:
    [Unit]
    Description=mautrix-$bridge bridge
    
    [Service]
    # N.B. If you didn't create a user with the correct home directory, set this
    #      to the directory where config.yaml is (e.g. /opt/mautrix-$bridge).
    WorkingDirectory=~
    ExecStart=/opt/mautrix-$bridge/bin/python -m mautrix_$bridge
    User=mautrix-$bridge
    
    [Install]
    WantedBy=multi-user.target
    

Optional dependencies

Usage

Production setup

The pip install URLs in the production setup guide include [all] at the end by default, which means all optional dependencies will be installed by default.

If you only want specific optional dependencies, replace the all with a comma-separated list of the pip extra names (e.g. sqlite,speedups).

If you don't want any optional dependencies, just remove the [all].

Development setup

To install all optional dependencies, use pip install --upgrade -r optional-requirements.txt.

To install specific optional dependencies, install the packages listed "Required packages" from the table of optional dependencies below. You can also check the expected versions of the packages from optional-requirements.txt.

Docker

The docker images contain all optional dependencies. Currently they can't be easily disabled.

List of optional dependencies

The symbol means you must also enable the feature in the config. Required packages in parentheses indicate a large dependency of the other packages.

All Python bridges

pip extra nameRequired packagesDescription
metricsprometheus_clientPrometheus metrics.
e2bepython-olm
pycryptodome
unpaddedbase64
End-to-bridge encryption support (see native dependency below).
sqliteaiosqliteExperimental SQLite support (currently in Telegram/Facebook/Signal)

N.B. python-olm requires libolm3 with dev headers, Python dev headers, and a C compiler. This means libolm-dev, python3-dev and build-essential on Debian-based distros.

If you want to avoid the dev headers, you can install the libolm3 package without -dev and get a pre-compiled python-olm from gitlab.matrix.org's PyPI registry. However, this method has not been tested properly, so it might not work at all.

pip install python-olm --extra-index-url https://gitlab.matrix.org/api/v4/projects/27/packages/pypi/simple

mautrix-telegram

pip extra nameRequired packagesDescription
speedupscryptg
cchardet
aiodns
brotli
Speed up some things, e.g. by using native crypto code.
qr_loginqrcode
Pillow
Telegram login by scanning a QR code from another device.
formattednumbersphonenumbersFormat phone numbers nicely in contact share messages

mautrix-facebook

pip extra nameRequired packagesDescription
animated_stickersPillowFinds the dimensions of stickers bridged from Facebook.
proxypysocks
aiohttp-socks
Support for proxying all Facebook traffic through a SOCKS5 proxy.

mautrix-instagram

pip extra nameRequired packagesDescription
imageconvertPillowConvert images from Matrix into JPEG so Instagram would accept them.

mautrix-signal

pip extra nameRequired packagesDescription
formattednumbersphonenumbersFormat phone numbers nicely before using as displaynames.
qrlinkqrcode
Pillow
Generate QR codes required for linking as a secondary device.
stickerssignalstickers-clientEnable bridging of Signal stickers to Matrix.

Manhole

The "manhole" allows server administrators to access a Python shell on a running bridge. This is a very powerful mechanism for administration and debugging.

To enable it, update the config and restart the bridge: set manhole -> enabled to true and manhole -> whitelist to the list of system UIDs you want to allow connecting. After that, send open-manhole <uid> to the bridge bot on Matrix as a bridge admin user to open the manhole for the given UID.

When the manhole is open, you can open a connection with nc -NU /var/tmp/mautrix-telegram.manhole in your normal shell. Optionally, install rlwrap and use rlwrap nc instead of nc to get a nicer prompt. To use rlwrap with Docker, run it on the host: rlwrap docker exec -i nc -NU /var/tmp/mautrix-telegram.manhole. Alternatively, you can mount the socket to the host and connect with the hosts netcat and rlwrap.

To close the connection, use Ctrl+C or exit(). To close the manhole, use the close-manhole management command or use manhole.close() inside the manhole.

Inside the manhole, bridge refers to the main class instance. Refer to the source code to see how everything works. The manhole supports top-level await expressions similar to python -m asyncio.

mautrix-telegram

Welcome to the mautrix-telegram docs!

Use the sidebar to find the relevant pages. Things that are the same for all bridges, like setting up the bridge, are right below the "Python-based bridges" header. Things specific to mautrix-telegram are under this page.

These docs are mostly targeted towards people who are hosting the bridge themselves. If you don't want to host it yourself, you can use a public instance. When using public instances, refer to their instructions and support rooms.

If you run a public instance and wish to list it here, please make a pull request.

Discussion

Matrix room: #telegram:maunium.net

Telegram chat: t.me/mautrix_telegram (bridged to Matrix room)

Authentication

Logging in

As logging in requires you to send the phone code and possibly also your 2FA password, make sure to run the commands in a management room (i.e. a room with no other users than you and the appservice bot).

If you have 2-factor auth enabled or if you are logging in with a bot token, you should use the web login, as otherwise the homeserver database will likely contain your password/token in plaintext form.

  1. Start a chat with the bridge bot (@telegrambot:example.com by default)
  2. Initiate the login process with login.
  3. The bot should tell you to use the web interface or login in-Matrix. If you have enabled both login modes in the config, the bot will give you both options.
  4. Choose the login method you want and follow the instructions under that heading, then go to the "Finally" section.

N.B. While the bridge uses the official client API, Telegram is known to ban suspicious users, and a brand new account using a 3rd party client is considered suspicious. Using a well-established account is perfectly safe. If you do get banned, Telegram usually reverts incorrect bans fairly quickly after emailing recover@telegram.org.

In-Matrix login

  1. Send your phone number to the room.
  2. The bot should prompt you to send your auth code to the room: send it once it does.
  3. If you have two-factor authentication enabled, again wait for the prompt and then send your password to the room.

Web login

New in version 0.2.0

  1. Click the link sent by the bot, enter your phone number and click "Request code".
  2. Enter your code and click "Sign in".
  3. If you have two-factor authentication enabled, enter your password and click "Sign in" again.

Bot token

New in version 0.3.0

You can also log in with your own relay bot. This is more limited than real accounts, but it means you can appear as yourself on Telegram without giving the bridge access to your real account.

In-Matrix

  1. Send your bot token to the room.

Web

  1. Click the link sent by the bot and click "Use bot token".
  2. Enter your bot token and click "Sign in".

Finally: If all went well, the bot should inform you of a successful login, and the bridge should start creating portal rooms for all your Telegram groups and invite you to them. The bridge won't automatically create rooms for private chats: see "Private messages" at the bottom of Creating and managing chats

Registering

Telegram officially discontinued registration from 3rd party clients as of 2023-02-18, so support for it was removed in v0.13.0 of the bridge. You should sign up using a mobile client and then log into the bridge.

You can safely uninstall the mobile client after the bridge is logged in. Telegram is not encrypted, so they don't have a concept of a primary device like WhatsApp and Signal do.

Logging out

Simply run the logout management command.

Creating and managing chats

Group chats and channels

New Matrix rooms for existing Telegram chats

New portal rooms for existing Telegram chats should be created when:

  1. The bridge is (re)started
  2. The user logs in to Telegram
  3. A message or an invite is received in the Telegram chat

If a portal room is somehow broken, you can tell the bridge to forget it using the command delete-portal. The portal room will be recreated when one of the conditions above is fulfilled. Note that automatic portal creation at startup/login is only for group chats. Private chat portals are only created when you receive a message or start a chat.

New Telegram chats for existing Matrix rooms

You can use the create command to create a new Telegram chat for an existing Matrix room.

Before running the command, make sure you have invited your appservice bot to the room, given the bot PL 100 and removed PL 100 from everyone else. As of 0.2.0, the bridge will function even without power levels.

Existing Telegram chats to existing Matrix rooms

New in version 0.2.0

Get the ID of the Telegram chat with the /id command of the relay bot. If you don't have/want a relay bot, figure out another way to get the telegram chat ID, and make sure it's prefixed (-100 for channels and - for chats). Then, run bridge <chat ID> in the room you want to bridge. Again, make sure that the appservice bot is in the room and the power levels are correct like in the previous section.

Matrix ⟷ Telegram mappings

Most Matrix actions are mapped to their Telegram counterparts. Joining, leaving, inviting and kicking are mapped as-is.

Basic power level bridging is implemented. However, there may be some issues that require restarting the bridge to properly sync the power levels. Also, the power level requirements are currently hardcoded as follows:

  • Normal groups
    • PL 0 = normal user
    • PL 50 = admin
    • PL 95 = creator
  • Supergroups and channels
    • PL 0 = normal user
    • PL 50 = moderator (i.e. admin who can't add other admins)
    • PL 75 = admin
    • PL 95 = creator

Private messages

Creating portals

There are three ways to create private chat portals:

  1. Start a normal Matrix DM (create a room and invite the Matrix ghost of the Telegram user), e.g. by finding a user in the user list of an already bridged group chat. The ghost should join and send confirmation of the portal creation.
  2. Use the pm command.
  3. Send or receive a message on another Telegram client.

Matrix ⟷ Telegram mappings

Most non-messaging Matrix actions are ignored in private chat portals. However, Leaving the portal will cause the portal room to be cleaned up and forgotten.

Bot commands

Initiating chats with bots is no different from initiating chats with real Telegram users.

New in version 0.2.0: The bridge will translate Matrix->Telegram bot commands at the start of the message from !command to /command.

Please note that when messaging a bot for the first time, it may expect you to run /start first. The bridge does not do this automatically.

Management commands

These commands can be used to interact with the bridge and Telegram in ways not supported by native Matrix actions.

Management commands only work if the bridge bot is in the room. This means that private chat portals do not currently support management commands.

If the room you send the command to does not have users other than you and the bridge bot, you do not have to prefix commands, i.e. you can literally write help to get the help message. If the room has more users, you must use the command prefix (!tg by default). For example: !tg help. The command prefix is always allowed even if it's not required.

Generic bridge commands

CommandUsage
helpShow this help message.
cancelCancel an ongoing action (such as login).
versionView the bridge name and version.

Authentication

Commands to authenticate with Telegram.

CommandUsage
login [mxid]Get instructions on how to log in. Admins can use the mxid parameter to log in as another user.
logoutLog out from Telegram.
login-matrixReplace your Telegram account's Matrix ghost with your own Matrix account.
ping-matrixPings the server with the stored matrix authentication.
pingCheck if you're logged into Telegram.
ping-botGet info of the message relay Telegram bot.
username <new username>Change your Telegram username.
session <list|terminate> [hash]View or delete other Telegram sessions.

Creating portals

Commands to make connections to Telegram chats.

CommandUsage
bridge [id]Bridge the current Matrix room to the Telegram chat with the given ID. The ID must be the prefixed version that you get with the /id command of the Telegram-side bot.
create [type]Create a Telegram chat of the given type for the current Matrix. type is either group, supergroup or channel. Defaults to group
pm <username>Open a private chat with the given Telegram user. You can also use a phone number instead of username, but you must have the number in your Telegram contacts for that to work.
join <link>Join a chat with an invite link. link is a complete t.me invite link, e.g. https://t.me/telegram

Portal management

Commands to manage the Telegram chats linked to portals. These can only be used in portal rooms and will directly affect the Telegram chat linked to the portal.

Most of these commands require some admin privileges in the Telegram chat: The bot will inform you if you do not have sufficient permissions.

CommandUsage
invite-linkGet a Telegram invite link to the current chat.
upgradeUpgrade a normal Telegram group to a supergroup.
group-name <name|->Change the username of a supergroup/channel. To disable, use a dash (-) as the name.
delete-portalRemove all users from the current portal room and forget the portal. Only works for group chats; to delete a private chat portal, simply leave the room.
unbridgeRemove ghosts from the current portal room and forget the portal.

Portal configuration

Some bridge settings can be set on a per-portal basis. The !tg config command is used for that.

CommandUsage
helpView this help text.
viewView the current config data.
defaultsView the default config values.
set <key> <value>Set a config value.
unset <key>Remove a config value.
add <key> <value>Add a value to an array.
del <key> <value>Remove a value from an array.

Miscellaneous things

CommandUsage
search [-r|--remote] <query>Search your contacts or the Telegram servers for users.
sync [chats|contacts|me]Synchronize your chat portals, contacts and/or own info.
sync-stateFetch Matrix room state to ensure the bridge has up-to-date info.
idGet the ID of the Telegram chat where this room is bridged.
play <play ID>Play a Telegram game.
caption <caption>Set a caption for the next image or file you send.

Administration

Bridge admin commands that do advanced things.

CommandUsage
filter-mode <whitelist|blacklist>Change whether the bridge will allow or disallow bridging rooms by default.
filter <whitelist|blacklist> <chat ID>Allow or disallow bridging a specific chat.
clean-roomsClean up unused portal/management rooms.
set-pl <level> [mxid]Set a temporary power level without affecting Telegram.
clear-db-cache <portal|puppet|user>Clear internal database caches
reload-user [mxid]Reload and reconnect a user

Relay bot

New in version 0.2.0

The bridge supports using a Telegram bot to relay messages for unauthenticated users, allowing Matrix users who are not logged into their Telegram account to chat with Telegram users.

Setup

  1. If you haven't yet, create a new bot on Telegram by chatting with @BotFather.
    • Make sure you disable privacy mode using BotFather's /setprivacy command in order to allow the bot to read messages in groups.
    • If you added the bot to a group before disabling privacy mode, you'll have to remove the bot and re-add it to apply the change.
  2. Configure the bridge to use the bot you created by setting the token you got from BotFather in the telegrambot_token field in the bridge's config.
  3. Restart the bridge and check status with the !tg ping-bot command on Matrix.
  4. Invite the relaybot to groups where you want it to bridge messages from unauthenticated Matrix users. If you're logged in to the bridge, you can use !tg ping-bot, click the user pill and click invite directly. If not, you can add the bot on the Telegram side.

If the room was created by the bridge and you don't have invite permissions, you can either use !tg set-pl to give yourself permissions, or /invite <mxid> (on Telegram) to invite users through the bridge bot.

Creating relaybot portals from Telegram

You can also create portals from Telegram if you have the relay bot set up and have allowed creating portals from telegram in the config (bridgerelaybotauthless_portals). Simply invite the relay bot to your Telegram chat and use the /portal command. If the chat is public, the bot should create the portal and reply with a room alias. If the chat is private, you'll need to invite Matrix users manually with /invite <mxid>.

Message format configuration

The format of messages and membership events that the bot sends to Telegram can be configured both bridge-wide and per-room. Per-room configs can be managed using the !tg config command.

For example, to disable bridging of membership events in a room, you can run

!tg config set state_event_formats join: ''
leave: ''
name_change: ''

which sets the state_event_formats config option to an object containing the empty strings join, leave and name_change.

Commands

CommandUsage
/invite [mxid]Invite a Matrix user to the portal room.
/portalCreate the portal if it does not exist and get the join info.
/idGet the prefixed ID of the chat that can be used with !tg bridge and !tg filter in Matrix
/mxbanBan a relayed Matrix user. Must reply to a relaybot message.
/mxkickSame as mxban, but for kicking

If you have your own Telegram bot for the bridge, you can copy this to the /setcommands BotFather command:

invite - Invite a Matrix user to the portal room.
portal - Create the portal if it does not exist and get the join info.
id - Get the prefixed ID of the chat that can be used with `!tg bridge` and `!tg filter` in Matrix
mxban - Ban a relayed Matrix user. Must reply to a relaybot message.
mxkick - Kick a relayed Matrix user. Must reply to a relaybot message.

Provisioning API

New in version 0.3.0

The provisioning API can be used to sign in and out, and to create and remove bridges. It is primarily intended for integration managers, such as Dimension.

You can find the API spec at https://spec.mau.fi/mxtg-provisioning/

Migrating from Telematrix

New in version 0.3.0

Removed in version 0.11.0

When migrating from Telematrix, you'll need to configure mautrix-telegram to use the same user ID format and bridge bot username. Other options, such as the AS token, port, etc.. don't need to be the same.

The database migration script is located in mautrix_telegram/scripts/telematrix_import. It can be ran using

$ python -m mautrix_telegram.scripts.telematrix_import [arguments...]

The script has three arguments:

  • -b / --bot-id (required) - The internal numeric user ID of the relay bot. This can be fetched from https://api.telegram.org/bot<BOT TOKEN>/getMe
  • -c / --config - The path to the mautrix-telegram config. Used for the database path and might be used for other values in the future (e.g. auto-fetching the bot id). Defaults to config.yaml.
  • -t / --telematrix-database - The URL of the telematrix database. Defaults to sqlite database.db

DBMS migration

New in version 0.3.0

Removed in version 0.11.0 (may come back in a future version)

The bridge includes a simple script for migrating between database management systems. It simply reads the data from one database and inserts it into another database.

The script is located in mautrix_telegram/scripts/dbms_migrate. It can be ran using

$ python3 -m mautrix_telegram.scripts.dbms_migrate -f <source db> -t <target db>

Both <source db> and <target db> are full database URLs, e.g. sqlite:///mautrix-telegram.db and postgres://user:password@localhost/mautrixtelegram

Steps:

  1. Stop the bridge
  2. Update the database URI in the config
  3. Initialize the new database with alembic upgrade head
    • Inside Docker, you need to be in /opt/mautrix-telegram/ and you need to pass the path to the config with alembic -x config=/data/config.yaml upgrade head
  4. Run the database migration script
  5. Start the bridge again

mautrix-facebook

This bridge has been replaced by mautrix-meta

Welcome to the mautrix-facebook docs!

Use the sidebar to find the relevant pages. Things that are the same for all bridges, like setting up the bridge, are right below the "Python-based bridges" header. Things specific to mautrix-facebook are under this page.

These docs are mostly targeted towards people who are hosting the bridge themselves. If you don't want to host it yourself, you can use a public instance. When using public instances, refer to their instructions and support rooms.

If you run a public instance and wish to list it here, please make a pull request.

Discussion

Matrix room: #facebook:maunium.net

Authentication

  1. Open a private chat with the bridge bot. Usually @facebookbot:your.server

Web-based login

New in version 0.2.1

The bridge includes a web-based login interface to prevent the bridge and homeserver from seeing your Facebook password. The web interface must be enabled in the config to use this method (appservice->public). After web login is enabled, send login with no parameters to the bridge bot, click the link it gives, and use the login form on the website to log in.

In-Matrix login

  1. Send login <email> to the bridge bot.
    • N.B. Prior to v0.2.1, the syntax is login <email> <password>, rather than sending the password in a separate message.
  2. Send your password to the room.
  3. If you have 2FA enabled, the bot will ask you to send the 2FA token.
  4. Recent chats should now get portals automatically. Other chats will get portals as you receive messages.

Note that in some cases, Facebook might decide your account has suspicious activity and block you until you do some tasks like adding a phone number or resetting your password. In most cases, enabling two-factor authentication solves this. If that doesn't help, hosting the bridge at home or making it proxy all traffic through a residential IP can help further reduce suspiciousness. The bridge can run separately from Synapse, e.g. on a Raspberry Pi. It can also use the http_proxy environment variable for all Facebook traffic.

Upgrading to v0.2.0

Version 0.2.0 of the bridge includes some major breaking changes:

  • It uses a completely different Messenger API, which means that all users will have to log in again to keep using the bridge. Logging in happens using email and password instead of stealing cookies like before.
    • v0.2.1 includes a web-based login interface that encrypts your password in-browser to prevent the bridge from seeing it.
  • There are database schema changes that can not be reversed. Taking a backup before upgrading is recommended. Additionally, only PostgreSQL is supported, see below for SQLite migration instructions.

SQLite migration

If you're using SQLite and want to keep your existing portal rooms, you must migrate the database to Postgres first. If you don't care about keeping existing portal rooms, you can use clean-rooms for cleaning up old portals and enable backfilling in the config to get message history in the new portals.

The bridge includes a simple script similar to mautrix-telegram's DBMS migration:

$ python -m mautrix_facebook.db.legacy_migrate -f <source db> -t <target db>

Both <source db> and <target db> are full database URLs, e.g. sqlite:///mautrix-facebook.db and postgres://user:password@localhost/mautrixfacebook

Steps:

  1. Stop the bridge and update to v0.2.0
  2. Update the database URI in the config
  3. Initialize the new database with alembic upgrade head
  4. Run the database migration script
  5. Start the bridge again

mautrix-googlechat

Welcome to the mautrix-googlechat docs!

Use the sidebar to find the relevant pages. Things that are the same for all bridges, like setting up the bridge, are right below the "Python-based bridges" header. Things specific to mautrix-googlechat are under this page.

These docs are mostly targeted towards people who are hosting the bridge themselves. If you don't want to host it yourself, you can use a public instance. When using public instances, refer to their instructions and support rooms.

If you run a public instance and wish to list it here, please make a pull request.

Discussion

Matrix room: #googlechat:maunium.net

Authentication

  1. Open a private chat with the bridge bot. Usually @googlechatbot:your.server.
  2. Open https://chat.google.com in a private browser window and log in normally, then extract cookies:
    1. Press F12 to open developer tools.
    2. select the "Application" (Chrome) or "Storage" (Firefox) tab.
    3. In the sidebar, expand "Cookies" and select https://chat.google.com.
    4. In the cookie list, find the COMPASS, SSID, SID, OSID and HSID rows.
      • When using Firefox, you may have multiple COMPASS cookies with different paths. Pick the one where path is /.
    5. Form a JSON object with the extracted cookies. It should look something like this (field names are case-insensitive):
      {
        "compass": "dynamite-ui=...",
        "ssid": "...",
        "sid": "...",
        "osid": "...",
        "hsid": "..."
      }
      
    6. Close the browser window to prevent the cookies being invalidated (Google uses refresh tokens, so you need to close the window within a few minutes).
  3. Send login-cookie {the json object} to the bot.
  4. Recent chats should now get portals automatically. Other chats will get portals as you receive messages.

mautrix-twitter

Welcome to the mautrix-twitter docs!

Use the sidebar to find the relevant pages. Things that are the same for all bridges, like setting up the bridge, are right below the "Python-based bridges" header. Things specific to mautrix-twitter are under this page.

These docs are mostly targeted towards people who are hosting the bridge themselves. If you don't want to host it yourself, you can use a public instance. When using public instances, refer to their instructions and support rooms.

If you run a public instance and wish to list it here, please make a pull request.

Discussion

Matrix room: #twitter:maunium.net

Authentication

  1. Open a private chat with the bridge bot. Usually @twitterbot:your.server.
  2. Log into Twitter in a private browser window.
  3. Send login-cookie to start the login.
  4. The bot will send you these instructions to extract required cookies. Follow the steps to log into the bridge:
    1. Press F12 to open developer tools.
    2. Select the "Application" (Chrome) or "Storage" (Firefox) tab.
    3. In the sidebar, expand "Cookies" and select https://twitter.com.
    4. In the cookie list, find the auth_token row and double click on the value then copy the value and send it to the room.
    5. Repeat the previous step with the ct0 row.
  5. Recent chats should now get portals automatically. Other chats will get portals as you receive messages.

mautrix-instagram

This bridge has been replaced by mautrix-meta

Welcome to the mautrix-instagram docs!

Use the sidebar to find the relevant pages. Things that are the same for all bridges, like setting up the bridge, are right below the "Python-based bridges" header. Things specific to mautrix-instagram are under this page.

These docs are mostly targeted towards people who are hosting the bridge themselves. If you don't want to host it yourself, you can use a public instance. When using public instances, refer to their instructions and support rooms.

If you run a public instance and wish to list it here, please make a pull request.

Discussion

Matrix room: #instagram:maunium.net

Authentication

  1. Open a private chat with the bridge bot. Usually @instagrambot:your.server
  2. Send login <email> <password>
  3. If you have 2FA enabled, the bot will ask you to send the 2FA code. If you don't have 2FA enabled, Instagram will likely ask you to confirm the login, in which case the bot will ask you to send the confirmation code.
  4. Recent chats should now get portals automatically. Other chats will get portals as you receive messages.

Go bridge setup

Please select a bridge above. If you don't select a bridge, you'll have to manually replace occurrences of $bridge with the correct name before running commands.

This page contains instructions for setting up the bridge by running the executable yourself. You may also want to look at the other ways to run the bridge:

Please note that everything in these docs are meant for server admins who want to self-host the bridge. If you're just looking to use the bridges, check out Beeper, which provides fully managed instances of all of these bridges.

If you need help with setting up the bridge, you can ask in the Matrix room: #$bridge:maunium.net. For help with setting up other parts like the homeserver that aren't the bridge, refer to their documentation to find support rooms.

Step 0: Requirements

  • A Matrix homeserver that supports application services (e.g. Synapse). You need access to register an appservice, which usually involves editing the homeserver config file.
  • A PostgreSQL server, v10 or higher (which you should already have for Synapse).
    • Make sure you don't share databases between unrelated programs. Shared postgres instance is fine, but shared database is not.
  • mautrix-whatsapp: A WhatsApp client running on a phone (both physical and virtual phones work).
  • mautrix-signal: A Signal client that can add linked devices (both official mobile apps and some unofficial clients like signal-cli work).
  • mautrix-signal: ffmpeg (if you want to send/receive voice messages).
  • mautrix-whatsapp: ffmpeg (if you want to send gifs from Matrix).
  • mautrix-discord: LottieConverter if you want to receive animated stickers.

If you want to compile the bridge manually (which is not required), you'll also need:

  • Go 1.21+ (download & installation instructions at https://go.dev/doc/install).
  • libolm3 with dev headers and a C/C++ compiler (if you want end-to-bridge encryption).
  • mautrix-signal: Rust, Cargo, libclang-dev and protoc (if you want to compile libsignal yourself).

Step 1: Installation

You may either compile the bridge manually or download a prebuilt executable from the mau.dev CI or GitHub releases. Prebuilt executables are the simplest option, as they don't require having Go nor libolm installed.

Option 1: Downloading a prebuilt executable from CI

  1. Download the relevant artifacts:
  2. Extract the downloaded zip file into a new directory.

Option 2: Downloading a release

  1. Go to https://github.com/mautrix/$bridge/releases
  2. Download the binary for the architecture you want and save it in a new directory.

Option 3: Compiling manually

  1. Clone the repo with git clone https://github.com/mautrix/$bridge.git mautrix-$bridge
  2. Enter the directory (cd mautrix-$bridge)
  3. Run ./build.sh to fetch Go dependencies and compile (build.sh will simply call go build with some additional flags).
    • If you want end-to-bridge encryption, make sure you have a C/C++ compiler and the Olm dev headers (libolm-dev on debian-based distros) installed.
    • If not, use ./build.sh -tags nocrypto to disable encryption.
      • Note: signal's build.sh script doesn't support extra arguments yet, so you have to use build-go.sh manually after building libsignal-ffi.a.
    • As an experimental feature, you can also use -tags goolm to use a pure Go reimplementation of libolm. Encryption can be supported without a C compiler or Olm dev headers with this method.

For mautrix-signal, if you don't want to compile libsignal yourself, you can download a precompiled libsignal_ffi.a from the mau.dev CI and place it in /usr/local/lib (or some other directory set in LIBRARY_PATH). Download links: Linux amd64, Linux arm64, macOS arm64

Step 2: Configuring and running

  1. Copy example-config.yaml to config.yaml
  2. Update the config to your liking. See the initial bridge config page for recommendations.
  3. Generate the appservice registration file by running ./mautrix-$bridge -g.
    • You can use the -c and -r flags to change the location of the config and registration files. They default to config.yaml and registration.yaml respectively.
  4. Register the bridge on your homeserver (see Registering appservices).
  5. Run the bridge with ./mautrix-$bridge.

Updating

If you compiled manually, pull changes with git pull and recompile with ./build.sh.

If you downloaded a prebuilt executable, simply download a new one and replace the old one.

Finally, start the bridge again.

systemd service

  1. Create a user for the bridge:
    $ sudo adduser --system mautrix-$bridge --home /opt/mautrix-$bridge
    
  2. Follow the normal setup instructions above. Make sure you use that user and home directory for the bridge.
  3. Create a systemd service file at /etc/systemd/system/mautrix-$bridge.service:
    [Unit]
    Description=mautrix-$bridge bridge
    
    [Service]
    Type=exec
    User=mautrix-$bridge
    WorkingDirectory=/opt/mautrix-$bridge
    ExecStart=/opt/mautrix-$bridge/mautrix-$bridge
    Restart=on-failure
    RestartSec=30s
    
    # Optional hardening to improve security
    ReadWritePaths=/opt/mautrix-$bridge
    NoNewPrivileges=yes
    MemoryDenyWriteExecute=true
    PrivateDevices=yes
    PrivateTmp=yes
    ProtectHome=yes
    ProtectSystem=strict
    ProtectControlGroups=true
    RestrictSUIDSGID=true
    RestrictRealtime=true
    LockPersonality=true
    ProtectKernelLogs=true
    ProtectKernelTunables=true
    ProtectHostname=true
    ProtectKernelModules=true
    PrivateUsers=true
    ProtectClock=true
    SystemCallArchitectures=native
    SystemCallErrorNumber=EPERM
    SystemCallFilter=@system-service
    
    [Install]
    WantedBy=multi-user.target
    

mautrix-whatsapp

Welcome to the mautrix-whatsapp docs!

Use the sidebar to find the relevant pages. Things that are the same for all bridges, like setting up the bridge, are right below the "Go-based bridges" header. Things specific to mautrix-whatsapp are under this page.

These docs are mostly targeted towards people who are hosting the bridge themselves. If you don't want to host it yourself, you can use a public instance. When using public instances, refer to their instructions and support rooms.

If you run a public instance and wish to list it here, please make a pull request.

Discussion

Matrix room: #whatsapp:maunium.net

In case you need to upload your logs somewhere, be aware that they contain your contacts' and your phone numbers. Strip them out with | sed -r 's/[0-9]{10,}/📞/g'

Android VM setup (not recommended)

This page documents how to setup WhatsApp in an Android Virtual Machine. It could be useful if you cannot or don't want to run WhatsApp on a mobile phone.

Running in a virtual machine is more suspicious and increases the risk of getting banned. A physical phone is recommended.

Requirements

You'll need a working Android SDK. You can use the Android Studio suite or only the command-line tools.

Currently the Android SDK appears to only work with OpenJDK8 (not 11), which will need to be installed.

WhatsApp in the Android VM will need to scan a QR-code via webcam. We can do this by passing the emulator a real webcam (which needs to be able to see the emulator screen) or a virtual one (e.g. v4l2loopback).

Android Virtual Device

Let's create an AVD (Android Virtual Device), get its webcam working and install WhatsApp.

Create the AVD

WhatsApp currently requires Android 2.3.3 (API 10) or newer. If you wish to install WhatsApp through the play store, choose a System Image supporting Google APIs.

The AVD will be created in ~/.android/avd/, regardless of the method you use to create it.

Using Android Studio

Android Studio offers an AVD Manager interface to create, configure and run AVDs.

Via command line

It's also possible to create an emulated android device from command line, using avdmanager from the Android SDK:

Install the required SDK components

Change the version and architecture as required. If your host has KVM support you should use the x86 target. If you don't have KVM support (running in certain VPS systems) then you will need to use the ARM targets.

./tools/bin/sdkmanager --install platform-tools emulator
./tools/bin/sdkmanager --install "platforms;android-24"
./tools/bin/sdkmanager --install "system-images;android-24;default;armeabi-v7a"

Once you have the SDK setup you can create a VM.

./tools/bin/avdmanager create avd -n AVD_NAME -k SDK_ID
e.g.:
./tools/bin/avdmanager create avd -n MyWhatsAppVM -k "system-images;android-24;default;armeabi-v7a"
or when using KVM (includes Play Store):
./tools/bin/avdmanager create avd -n MyWhatsAppVM -k "system-images;android-28;google_apis_playstore;x86"

Run the AVD using the Android Emulator:

./emulator/emulator -show-kernel -no-boot-anim -avd AVD_NAME

Set up the webcam

Android Emulator seems to support different ways to feed custom images/videos to the Android Webcam, but somehow the only one that worked for me is passing it the host's camera (i.e. using webcam0 camera mode).

webcam0 seems to always be connected to the device /dev/video0. In case you wish to use a different device (e.g. /dev/video2), rename it to /dev/video0:

# needed if you want to feed /dev/video2 to the AVD
sudo mv /dev/video0 /dev/video0.original
sudo mv /dev/video2 /dev/video0

Set the back-camera mode to webcam0, either using the AVD Manager GUI (enable Show Advanced Settings) or by running the emulator from command line:

./emulator/emulator -avd AVD_NAME -camera-back webcam0

Use v4l2loopback to create a virtual camera

Install the v4l2loopback module and load it:

modprobe v4l2loopback

This should create a new /dev/video* file. If needed, rename it as /dev/video0.

Feed your desktop into this virtual webcam, e.g. using:

ffmpeg -f x11grab -s 640x480 -i :0.0+10,20 -vf format=pix_fmts=yuv420p -f v4l2 /dev/video0

You can test whether it's working by watching your webcam (e.g. with mplayer tv:// -tv driver=v4l2:device=/dev/video0).

The AVD's webcam should now show a piece of your desktop. When mautrix-whatsapp (or WhatsApp Web) shows you the QR-code, just move its window onto the shared desktop area.

Boot the AVD

You can boot the AVD using the AVD Manager GUI, or calling the emulator via command line:

./emulator/emulator -avd AVD_NAME [options]

Some useful options include:

  • -no-audio and -no-window, to disable audio and video for the VM. With these the emulator can run headless.
  • -show-kernel, display the console boot text instead of showing the boot GUI
  • -no-snapshot, to cold boot the AVD (i.e. it will ignore some cache).
  • -memory MB, to reduce the amount of RAM passed to the AVD. Unfortunately the emulator will force a minimum amount.

Install WhatsApp

WhatsApp will only work with your phone number on a device per time: when you log in on your AVD, the other devices will disconnect. If you install WhatsApp on an AVD and copy such AVD, both copies will be able to run WhatsApp (although not at the same time!)

WhatsApp can be installed using Google Play, if available, or with an APK. In the latter case, just drag'n'drop the APK on the emulator's window or run adb install APK_FILE.

In the 'Chats screen' goto 'Menu' > 'WhatsApp Web'. You can follow the instructions and use WhatsApp Web to verify whether all is working.

You can now proceed to link your virtual android to the bridge as described on the authentication Page.

NOTE: The bridge will cycle through multiple QR codes, you have to have everything ready to go. You won't have time to copy/paste images so you will need the VM and the Matrix client running on the same desktop.

After installation has finished ensure you open Android settings -> "Battery" -> "All Apps" -> find WhatsApp and choose: "Do not optimize".

Running Headless

Once everything is working, you can run the Android emulator headless, and even copy it to a server or whatever.

Make sure that all the software we need (Android SDK, Emulator, system images etc) is installed on the target host.

Copy your AVD over there (from ~/.android/avd/AVD_NAME.* on your system to the ~/.android/avd/ of the target host).

Run the emulator headless:

./emulator/emulator -avd AVD_NAME -no-audio -no-window [options]

If your AVD goes into sleep-mode or becomes unresponsive, you can try to reboot it or to start WhatsApp main activity running am start -n 'com.whatsapp/.HomeActivity' on your AVD (or e.g. adb [-s DEVICE_SERIAL] shell am start -n 'com.whatsapp/.HomeActivity' directly on your shell).

Required packages

Here a list of the packages you'll need to install on various Linux distributions to make everything work.

Arch Linux

Either install android-studioAUR or:

You might want to choose jdk8-openjdk as java-environment, as newer JDKs seem to have problems with the Android stuff.

Note that android-studio and the other packages aren't fully compatible: the system image path will differ slightly and you'll need to fix it in the AVD config.ini files: image.sysdir.1 should be something like system-images/android-15/default/x86/ for Android Studio images, and system-images/android-15/x86/ for AUR system images. The emulator will otherwise fail printing Cannot find AVD system path. Please define ANDROID_SDK_ROOT.

Install v4l2loopback-dkms-gitAUR to use v4l2loopback.

Authentication

  1. Open a private chat with the bridge bot. Usually @whatsappbot:your.server
  2. Send login to start the login.
    • New in version 0.10.1: To log in by entering a 8-letter code on your phone instead of scanning the QR code, pass your phone number after the command, e.g. login +123456789.
  3. Log in by scanning the QR code or entering the pairing code. If the code expires before you scan it, the bridge will send an error to notify you.
    1. Open WhatsApp on your phone.
    2. Tap Menu or Settings and select Linked devices.
    3. Point your phone at the image sent by the bot to capture the code.
      • If logging in with pairing code, tap "Link with a phone number instead".
  4. Finally, the bot should inform you of a successful login.
    • The bridge will start creating portal rooms approximately a minute after login. The amount of backfill can be configured before login, the default is to create portals for all chats from WhatsApp and backfill 50 messages in recent chats.

Please note that the bridge uses the web API. Prior to v0.2.0 and the multidevice update, your phone had to be connected to the internet for the bridge to work. After v0.2.0, it's enough if the phone is connected at least once every 2 weeks. If the phone is offline for >2 weeks, linked devices will become disconnected: https://faq.whatsapp.com/general/download-and-installation/about-linked-devices. The bridge will warn you if it doesn't receive any data from the phone in over 12 days.

N.B. WhatsApp is known to ban accounts that are too suspicious. Just using the bridge shouldn't cause any bans, but getting banned is more likely when combining the bridge with other suspicious activity (running WhatsApp in an Android emulator, using VoIP numbers, using a newly created account, initiating DMs to non-contacts, etc).

Logging out

Simply run the logout management command.

mautrix-discord

Welcome to the mautrix-discord docs!

Use the sidebar to find the relevant pages. Things that are the same for all bridges, like setting up the bridge, are right below the "Go-based bridges" header. Things specific to mautrix-discord are under this page.

These docs are mostly targeted towards people who are hosting the bridge themselves. If you don't want to host it yourself, you can use a public instance. When using public instances, refer to their instructions and support rooms.

If you run a public instance and wish to list it here, please make a pull request.

Discussion

Matrix room: #discord:maunium.net

Authentication

  1. Open a private chat with the bridge bot. Usually @discordbot:your.server
  2. Follow one of the three instructions below.

N.B. Discord may ban users who are too suspicious. The bridge shouldn't be too suspicious by default, but if you want to avoid any risk, use a bot account.

QR login

QR login is more convenient, but requires having the Discord mobile app installed, and may encounter CAPTCHAs which are currently not supported by the bridge.

  1. Send login-qr to start the login.
  2. Log in by scanning the QR code with a Discord mobile app.
    • After scanning the code, you'll need to approve the login on the mobile app. See the official docs for more info.
    • The app can be uninstalled afterwards.
  3. If you encounter a bad request error saying something about a captcha, you'll have to use token login. Otherwise the login should be successful.

Token login

You can also log in by logging in manually and providing the access token to the bridge.

  1. Log in to Discord in a browser. A private window is recommended so you can easily make the browser forget the token without invalidating it.
  2. Press F12 (or Cmd+Shift+I on Mac) to open developer tools.
  3. Select the "Network" tab and filter for api.
  4. Press F5 (or Cmd+R on Mac) to reload the page.
  5. Pick any successful request (e.g. the library request). Scroll down to "Request Headers" and find the Authorization header. Right-click the entry and choose copy value.
  6. Send login-token user <token> to the bot (replacing <token> with the copied value).
  7. (Close the private window)

Bot token login

If you don't want to use a real account, you can also log in as a bot.

  1. Create an application on https://discord.com/developers/applications.
  2. In the "Bot" section, add a bot and copy the token.
  3. Enable "server members intent" and "message content intent" under the "Privileged Gateway Intents" section.
  4. Send login-token bot <token>
  5. To add your bot to a guild, go to OAuth2 -> URL Generator, select "bot" under scopes and select the permissions to grant. The generated URL can then be used to add the bot to a guild.
    • At least "Send Messages", "Create Public Threads", "Send Messages in Threads", "Read Message History" and "Add Reactions" are recommended.
    • If you're the guild admin, you can just choose "Administrator" for the bot and not worry about exact permissions.

After a successful login, the bridge will create portals for some recent DMs (some is defined by startup_private_channel_create_limit in the bridge config). Messages can also be backfilled, but that is disabled by default (the limit is 0). To bridge guilds, use the guilds command.

Bridging rooms

By default, the bridge will create portals for a few recent direct messages after logging in. The startup_private_channel_create_limit option in the config defines the number of chats.

Entire guilds can be bridged using the guilds command. Use guilds status to view the list of guilds and get their IDs, then use guilds bridge <id> to bridge a guild. After bridging, spaces will be created automatically, and rooms will be created as necessary when messages come in. You can also pass --entire to the bridge command to immediately create all rooms.

If you want to manually bridge channels, invite the bot to the room you want to bridge and run !discord bridge <channel ID> to bridge the room. After that, you can also use !discord set-relay to set up relaying with webhooks.

Relaying with webhooks

New in version 0.2.0

The bridge supports using Discord's webhook feature to relay messages from Matrix users who haven't logged into the bridge.

Webhook relays can be used regardless of how you logged into Discord, a bot is not required. However, in the future the bridge may include additional relay integration using a bot.

If you want to use the bridge for relaying only, it is recommended to have a dedicated Matrix account to log in as the bot user. If you log in on your main Matrix account, your messages will be sent through the bot rather than through the webhook with custom profiles.

Setup

To enable relaying in a room, use !discord set-relay. The command requires a parameter, which can either be --create [name] or --url <url>.

The room must be bridged before running set-relay. You can either have the bridge create rooms by bridging the entire guild with the guilds command, or you can bridge individual channels using the bridge command. See the bridging rooms page for more info.

  • !discord set-relay --create will create a new webhook. You must be logged into the bridge as a user or bot that has privileges to create webhooks in on the Discord side.
    • You can optionally pass a name for the webhook after the command, e.g. !discord set-relay --create matrix bridge. The default name is "mautrix". Note that the name may not contain "discord".
  • !discord set-relay --url https://discord.com/api/webhooks/... will use the given webhook URL for relaying.
    • You can optionally specify a room ID before --url to run the command in a private room (and avoid leaking the webhook secret to everyone else in the room being bridged), e.g. !discord set-relay !26DmcJd3cQ...:example.com --url https://discord.com/...

Direct media access (v2)

New in version 0.7.0

To avoid spamming your homeserver's media repository with all files from Discord, the bridge has an option to generate fake mxc:// URIs that contain the Discord message ID and some other info. To make the URIs work, the bridge effectively turns into a media-only homeserver.

For example, if your main Matrix server is example.com, you could set up discord-media.example.com as the bridge's server name. The way to do that is to either proxy the subdomain to the bridge entirely, or create a .well-known/matrix/server file (just like normal homeserver setups) pointing somewhere else where the bridge is listening.

When proxying the subdomain entirely, port 443 is enough, the bridge will automatically serve a .well-known to redirect from port 8448 to 443.

Endpoints that the bridge can handle with direct media access:

  • /_matrix/media/*
  • /_matrix/key/*
  • /_matrix/federation/v1/version
  • /.well-known/matrix/server

To enable direct media access:

  • Set bridge -> direct_media -> enabled to true.
  • Change the server_name field to a (sub)domain where the aforementioned endpoints are proxied to the bridge.
  • Ensure server_key is set.
    • The bridge generates one automatically on startup and writes it back to the config. If you prevented the bridge from writing the config, you'll have to set it yourself so it wouldn't generate a new one on every restart.

Legacy direct media access

In the past, the bridge supported specifying templates that a simple reverse proxy could parse to redirect the request to Discord's CDN. However, that method no longer works since Discord started requiring signed URLs that expire.

Old docs

New in version 0.4.0

To avoid spamming your homeserver's media repository with all files from Discord, the bridge has an option to generate fake mxc:// URIs that contain the Discord media ID. The media repo or your reverse proxy can then handle those URIs specially to fetch content directly from the Discord CDN.

To enable this mode, set bridge -> media_patterns -> enabled to true in the bridge config. You can then configure each of the patterns or leave the defaults.

Ways to use patterns

Default pattern and MSC3860-compatible media repo

If your media repo supports MSC3860, you can use the default patterns out of the box with no modifications. Your media repo will act like discord-media.mau.dev is a federated Matrix server, so when your client requests Discord media, your media repo will ask discord-media.mau.dev, which redirects to cdn.discordapp.com. Your media repo then downloads the media and caches it as remote media (like it does for all federated media).

MSC3860 is supported as of Synapse v1.98.0 and matrix-media-repo v1.4.0. Additionally, while Conduit doesn't opt into redirects, it does follow them, so it should work with the default config.

The software on discord-media.mau.dev is just a Caddy instance with the first example config below, plus a static .well-known file to redirect federation to 443. You can find the raw config at mau.dev/maunium/caddy.

Redirect in reverse proxy

You can also configure your reverse proxy to redirect mxc://discord-media.mau.dev/* downloads directly to cdn.discordapp.com. This method doesn't involve your media repo at all, so it already works with most clients. However, it won't work with servers that don't support MSC3860, as they'd still try to connect to discord-media.mau.dev, which may be a problem if you want to use your bridge in federated rooms. Additionally, you may encounter some CORS issues with this method as cdn.discordapp.com doesn't provide CORS headers for all files (like webp images and non-inline documents)

Caddy config example
matrix.example.com {
	handle /_matrix/media/*/download/discord-media.mau.dev/* {
		# The redirect must have CORS headers to let web clients follow it.
		header Access-Control-Allow-Origin *
		# Need to use a route directive to make the uri mutations apply before redir
		route {
			# Remove path prefix
			uri path_regexp ^/_matrix/media/.+/download/discord-media\.mau\.dev/ /
			# The mxc patterns use | instead of /, so replace it first turning the path into attachments/1234/5678/filename.png
			uri replace "%7C" /
			# Then redirect to cdn.discordapp.com/attachments/1234/5678/filename.png with HTTP 307
			redir https://cdn.discordapp.com{uri} 307
		}
	}
	# Special-case stickers because they don't have CORS headers on cdn.discordapp.com for some reason
	handle /_matrix/media/*/download/discord-media.mau.dev/stickers|* {
		header Access-Control-Allow-Origin *
		route {
			uri path_regexp ^/_matrix/media/.+/download/discord-media\.mau\.dev/ /
			uri replace "%7C" /
			redir https://media.discordapp.net{uri} 307
		}
	}
	# Do the same for thumbnails, but redirect to media.discordapp.net (which is Discord's thumbnailing server, and happens to use similar width/height params as Matrix)
	# Alternatively, you can point this at cdn.discordapp.com too. Clients shouldn't mind even if they get a bigger image than they asked for.
	handle /_matrix/media/*/thumbnail/discord-media.mau.dev/* {
		header Access-Control-Allow-Origin *
		route {
			uri path_regexp ^/_matrix/media/.+/thumbnail/discord-media\.mau\.dev/ /
			uri replace "%7C" /
			redir https://media.discordapp.net{uri} 307
		}
	}
	# The usual proxying to your homeserver
	handle /_matrix/* {
		reverse_proxy http://localhost:8008
	}
}
Nginx config example
server {
	listen 443;
	server_name matrix.example.com;
	# ... usual /_matrix location block and other stuff ...
	# N.B. If you use a regex pattern for the /_matrix block, it must be below these locations

	location ~ ^/_matrix/media/(?:v3|r0)/download/discord-media.mau.dev/attachments\|([0-9]+)\|([0-9]+)\|(.+)$ {
		add_header Access-Control-Allow-Origin *;
		return 307 https://cdn.discordapp.com/attachments/$1/$2/$3;
	}
	location ~ ^/_matrix/media/(?:v3|r0)/download/discord-media.mau.dev/emojis\|(.+)$ {
		add_header Access-Control-Allow-Origin *;
		return 307 https://cdn.discordapp.com/emojis/$1;
	}
	location ~ ^/_matrix/media/(?:v3|r0)/download/discord-media.mau.dev/stickers\|(.+)$ {
		add_header Access-Control-Allow-Origin *;
		# Stickers don't have CORS headers on cdn.discordapp.com for some reason, so always use media.
		return 307 https://media.discordapp.net/stickers/$1;
	}
	location ~ ^/_matrix/media/(?:v3|r0)/download/discord-media.mau.dev/avatars\|([0-9]+)\|(.+)$ {
		add_header Access-Control-Allow-Origin *;
		return 307 https://cdn.discordapp.com/avatars/$1/$2;
	}

	# Thumbnails (optional-ish)
	location ~ ^/_matrix/media/(?:v3|r0)/thumbnail/discord-media.mau.dev/attachments\|([0-9]+)\|([0-9]+)\|(.+)$ {
		add_header Access-Control-Allow-Origin *;
		return 307 https://media.discordapp.net/attachments/$1/$2/$3?$args;
	}
	location ~ ^/_matrix/media/(?:v3|r0)/thumbnail/discord-media.mau.dev/emojis\|(.+)$ {
		add_header Access-Control-Allow-Origin *;
		return 307 https://media.discordapp.net/emojis/$1?$args;
	}
	location ~ ^/_matrix/media/(?:v3|r0)/thumbnail/discord-media.mau.dev/stickers\|(.+)$ {
		add_header Access-Control-Allow-Origin *;
		return 307 https://media.discordapp.net/stickers/$1?$args;
	}
	location ~ ^/_matrix/media/(?:v3|r0)/thumbnail/discord-media.mau.dev/avatars\|([0-9]+)\|(.+)$ {
		add_header Access-Control-Allow-Origin *;
		return 307 https://media.discordapp.net/avatars/$1/$2?$args;
	}
}

Proxy in reverse proxy

If you want bridged media to work over federation without MSC3860, you can change discord-media.mau.dev to your own server name, and have your reverse proxy actually proxy the downloads instead of just redirecting to cdn.discordapp.com. That way it'll work with all existing servers and clients. The downside of this method is the higher bandwidth use compared to redirecting, and theoretical abuse vectors for spamming the Discord CDN through your server.

When using this example, change discord-media.mau.dev/ in the patterns to example.com/discord_ (replacing example.com with your own domain). The discord_ prefix is there so that other media on your domain will still work normally.

Caddy config example
matrix.example.com {
	handle /_matrix/media/*/download/example.com/discord_* {
		header Access-Control-Allow-Origin *
		# Remove path prefix
		uri path_regexp ^/_matrix/media/.+/download/example\.com/discord_ /
		# The mxc patterns use | instead of /, so replace it first turning it into attachments/1234/5678/filename.png
		uri replace "%7C" /
		reverse_proxy {
			# reverse_proxy automatically includes the uri, so no {uri} at the end
			to https://cdn.discordapp.com
			# Caddy doesn't set the Host header automatically when reverse proxying
			# (because usually reverse proxies are local and don't care about Host headers)
			header_up Host cdn.discordapp.com
		}
	}
	# Do the same for thumbnails, but redirect to media.discordapp.net (which is Discord's thumbnailing server, and happens to use similar width/height params as Matrix)
	# Alternatively, you can point this at cdn.discordapp.com too. Clients shouldn't mind even if they get a bigger image than they asked for.
	handle /_matrix/media/*/thumbnail/example.com/discord_* {
		header Access-Control-Allow-Origin *
		uri path_regexp ^/_matrix/media/.+/thumbnail/example\.com/discord_ /
		uri replace "%7C" /
		reverse_proxy {
			to https://media.discordapp.net
			header_up Host media.discordapp.net
		}
	}
	handle /_matrix/* {
		reverse_proxy http://localhost:8008
	}
}
Nginx config example
server {
	listen 443;
	server_name matrix.example.com;
	# ... usual /_matrix location block and other stuff ...
	# N.B. If you use a regex pattern for the /_matrix block, it must be below these locations

	# You may need to configure a resolver for nginx to be able to resolve cdn.discordapp.com
	#resolver 8.8.8.8;

	location ~ ^/_matrix/media/(?:v3|r0)/download/example.com/discord_attachments\|([0-9]+)\|([0-9]+)\|(.+)$ {
		add_header Access-Control-Allow-Origin *;
		proxy_set_header Host cdn.discordapp.com;
		proxy_pass https://cdn.discordapp.com/attachments/$1/$2/$3;
	}
	location ~ ^/_matrix/media/(?:v3|r0)/download/example.com/discord_emojis\|(.+)$ {
		add_header Access-Control-Allow-Origin *;
		proxy_set_header Host cdn.discordapp.com;
		proxy_pass https://cdn.discordapp.com/emojis/$1;
	}
	location ~ ^/_matrix/media/(?:v3|r0)/download/example.com/discord_stickers\|(.+)$ {
		add_header Access-Control-Allow-Origin *;
		proxy_set_header Host cdn.discordapp.com;
		proxy_pass https://cdn.discordapp.com/stickers/$1;
	}
	location ~ ^/_matrix/media/(?:v3|r0)/download/example.com/discord_avatars\|([0-9]+)\|(.+)$ {
		add_header Access-Control-Allow-Origin *;
		proxy_set_header Host cdn.discordapp.com;
		proxy_pass https://cdn.discordapp.com/avatars/$1/$2;
	}

	# Thumbnails (optional-ish)
	location ~ ^/_matrix/media/(?:v3|r0)/thumbnail/example.com/discord_attachments\|([0-9]+)\|([0-9]+)\|(.+)$ {
		add_header Access-Control-Allow-Origin *;
		proxy_set_header Host media.discordapp.net;
		proxy_pass https://media.discordapp.net/attachments/$1/$2/$3?$args;
	}
	location ~ ^/_matrix/media/(?:v3|r0)/thumbnail/example.com/discord_emojis\|(.+)$ {
		add_header Access-Control-Allow-Origin *;
		proxy_set_header Host media.discordapp.net;
		proxy_pass https://media.discordapp.net/emojis/$1?$args;
	}
	location ~ ^/_matrix/media/(?:v3|r0)/thumbnail/example.com/discord_stickers\|(.+)$ {
		add_header Access-Control-Allow-Origin *;
		proxy_set_header Host media.discordapp.net;
		proxy_pass https://media.discordapp.net/stickers/$1?$args;
	}
	location ~ ^/_matrix/media/(?:v3|r0)/thumbnail/example.com/discord_avatars\|([0-9]+)\|(.+)$ {
		add_header Access-Control-Allow-Origin *;
		proxy_set_header Host media.discordapp.net;
		proxy_pass https://media.discordapp.net/avatars/$1/$2?$args;
	}
}

mautrix-slack

Welcome to the mautrix-slack docs!

Use the sidebar to find the relevant pages. Things that are the same for all bridges, like setting up the bridge, are right below the "Go-based bridges" header. Things specific to mautrix-slack are under this page.

These docs are mostly targeted towards people who are hosting the bridge themselves. If you don't want to host it yourself, you can use a public instance. When using public instances, refer to their instructions and support rooms.

If you run a public instance and wish to list it here, please make a pull request.

Discussion

Matrix room: #slack:maunium.net

Authentication

  1. Open a private chat with the bridge bot. Usually @slackbot:your.server.

Password login

Password login supports all Slack features, except portals for Slack conversations will not be created immediately after login. If your Slack account does not have a password you need to use token login or add a password to your account.

  1. Send the command login-password <email address> <slack team domain> <password>, for example login-password me@email.com workplacechat.slack.com hunter2.

Password login isn't guaranteed to always be fully supported, but you can switch to token-based login at any time afterwards by following the instructions in the Token login section without signing out.

Token login

Token login works for any Slack account and fully supports all Slack features.

  1. Login to the Slack web app in a browser, and acquire the authentication token and d cookie from inside the app.
    • The token starts with xoxc- and can be found using the browser devtools, in the app's local storage inside the localConfig_v2 object, as teams['your team ID'].token.
    • The cookie is named d, it starts with xoxd- and can be found in the cookies section in the devtools.
  2. Send the command login-token <token> <cookie>, for example login-token xoxc-tokengoeshere xoxd-cookiegoeshere

After login using a token, all your joined channels will automatically be bridged into Matrix, but DMs will only appear once you receive messages in them.

mautrix-gmessages

Welcome to the mautrix-gmessages docs!

Use the sidebar to find the relevant pages. Things that are the same for all bridges, like setting up the bridge, are right below the "Go-based bridges" header. Things specific to mautrix-gmessages are under this page.

These docs are mostly targeted towards people who are hosting the bridge themselves. If you don't want to host it yourself, you can use a public instance. When using public instances, refer to their instructions and support rooms.

If you run a public instance and wish to list it here, please make a pull request.

Discussion

Matrix room: #gmessages:maunium.net

Authentication

  1. Open a private chat with the bridge bot. Usually @gmessagesbot:your.server

QR login

This is the recommended method, as it's much easier to set up. If you switched to Google account pairing, you can switch back by unpairing all devices and then clicking the switch to QR pairing button that appears.

  1. Send login-qr to start the login.
  2. Log in by scanning the QR code. If the code expires before you scan it, the bridge will send an error to notify you.
    1. On your phone, open Messages by Google.
    2. Tap Menu from your conversation list and select Device pairing.
    3. Tap QR code scanner and point your phone at the image sent by the bot.
  3. Finally, the bot should inform you of a successful login.
    • The bridge will create portal rooms for recent chats. The number is configurable and defaults to 25 chats with 50 messages backfilled in each chat.

As all messages are proxied through the app, your phone must be connected to the internet for the bridge to work.

Google account login

New in version 0.3.0

This method is available as a fallback, it's not recommended since it's more difficult. This method still proxies everything through your phone, it just pairs in a different way.

Note that Google Fi's "sync to your Google Account" option is not supported by the bridge even with Google account login. You must choose the normal mode where RCS chats are available (option 1 in https://support.google.com/fi/answer/6188337).

  1. Send login-google to start the login.
  2. Log into https://accounts.google.com/AccountChooser?continue=https://messages.google.com/web/config with your Google account.
    • Using a private window is recommended to ensure the cookies don't get rotated by the bridge, and because the bridge doesn't support cookies linked to multiple accounts.
    • The continue URL in the link is chosen so that it would only log into your Google account and not try to pair the browser.
  3. Make a key-value JSON object containing at least the SID, HSID, SSID, OSID, APISID and SAPISID cookies. Sometimes Google also requires __Secure-1PSIDTS to be included.
  4. Send the JSON object to the bot.
  5. Open Google Messages on your phone and tap on the emoji the bridge bot sent.
  6. Finally, the bot should inform you of a successful login.

Logging out

Simply run the logout management command.

mautrix-signal

Welcome to the mautrix-signal docs!

Use the sidebar to find the relevant pages. Things that are the same for all bridges, like setting up the bridge, are right below the "Go-based bridges" header. Things specific to mautrix-signal are under this page.

These docs are mostly targeted towards people who are hosting the bridge themselves. If you don't want to host it yourself, you can use a public instance. When using public instances, refer to their instructions and support rooms.

If you run a public instance and wish to list it here, please make a pull request.

Discussion

Matrix room: #signal:maunium.net

Authentication

  1. Open a private chat with the bridge bot. Usually @signalbot:your.server

Linking as secondary device

  1. Go to "Linked Devices" in the Signal app settings and add a new device.
  2. Send login to the bridge bot.
  3. Scan the QR code the bridge sends you.
  4. Finally, the bot should inform you of a successful login.
    • Chats will not be immediately bridged currently, they will be bridged as you receive messages.
    • Signal does not support any kind of message history (even on official apps), so the bridge won't backfill any messages.

Registering as the primary device

Registering as the primary device is no longer supported directly in the bridge. Using an official mobile app is recommended. However, if you don't want to use those, signal-cli works quite well too.

signal-cli instructions
  1. Download the latest release of signal-cli.
  2. Run signal-cli -u +123456789 register
  3. Go to https://signalcaptchas.org/registration/generate.html to generate a captcha code.
    • The page will redirect you to a signalcaptcha:// URI after solving the captcha. At least on Firefox, you need to have the devtools console open to be able to see and copy the URI.
    • Alternatively, you can wait for a few seconds for the "Open Signal" button to appear, then right click on it and copy the link.
  4. Run signal-cli -u +123456789 register --captcha 'signalcaptcha://signal-hcaptcha...' with the generated captcha code.
  5. Run signal-cli -u +123456789 verify 123456 (123456 being the code sent over SMS).
  6. Send login to the bridge bot.
  7. Run signal-cli -u +123456789 addDevice --uri 'sgnl://...' with the URI returned by the bridge bot.
  8. The bot should inform you of a successful login.
  9. Run signal-cli -u +123456789 receive occasionally to make sure the registration remains active.

mautrix-meta

Welcome to the mautrix-meta docs!

Use the sidebar to find the relevant pages. Things that are the same for all bridges, like setting up the bridge, are right below the "Go-based bridges" header. Things specific to mautrix-meta are under this page.

These docs are mostly targeted towards people who are hosting the bridge themselves. If you don't want to host it yourself, you can use a public instance. When using public instances, refer to their instructions and support rooms.

  • There are no public instances yet, as this bridge is still highly experimental.

If you run a public instance and wish to list it here, please make a pull request.

Discussion

Matrix room: #meta:maunium.net

Authentication

  1. Open a private chat with the bridge bot. Usually @instagrambot:your.server or @facebookbot:your.server
  2. Send login to the bridge bot. The bot should ask you to paste cookies, which will happen in step 6.
  3. Open the website in a private window (facebook.com, messenger.com or instagram.com, depending on what you configured the bridge to use).
  4. Open browser devtools and go to the network tab. Select "XHR" as the request type and search for graphql.
  5. Log in normally.
  6. Right click one of the requests in devtools, choose "Copy" (Chrome) or "Copy Value" (Firefox), then "Copy as cURL".
    • Any request with the correct cookies should work, graphql is just used as an example that should be easy to find.
    • Note for windows users: Make sure to select "Copy as cURL (POSIX)", not "(Windows)", if given both options.
    • You can also find the cookies manually and send them to the bot as a simple key-value JSON object. The relevant cookies are:
      • Instagram: sessionid, csrftoken, mid, ig_did, ds_user_id
      • Facebook: datr, c_user, sb, xs
  7. Paste the copied data to the bridge bot.
  8. The bot should inform you of a successful login and sync recent chats.

N.B. In some cases, Meta may decide your account has suspicious activity and block you until you do some tasks like completing a captcha, adding a phone number or resetting your password. It is recommended to have two-factor authentication enabled to reduce the risk of such blocks.

Firefox devtools example Chrome devtools example

Migrating from mautrix-facebook

  1. Either create a new database for mautrix-meta, or rename the old one and create a new one with the original name.
  2. Configure mautrix-meta normally. You must use the same bot username and username template to migrate old chats. You can also just reuse the same registration file, as long as you keep the same hostname/port and as/hs_token.
    • You can't use the same config file as mautrix-facebook, make a new one and copy the relevant values.
    • Don't start the bridge yet.
  3. Run ./mautrix-meta --db-migrate-from postgres://user:pass@host/olddb (or --db-migrate-from /path/to/old.db for SQLite).
  4. Optionally, manually transfer the tables starting with crypto_ (just pg_dump + import to new db for each table).
  5. Run the bridge normally.

mautrix-instagram can not be migrated, you just have to delete the rooms and start with a fresh db. You can still reuse the username template if you want to.

mautrix-imessage

Welcome to the mautrix-imessage docs!

mautrix-imessage is a Matrix-iMessage puppeting bridge. The bridge has three different ways to connect to iMessage:

  • Normal Mac. Built into mautrix-imessage. Uses AppleScript to send, reads the iMessage SQLite database to receive, and uses Contacts.framework for contact list access.
  • Mac with SIP disabled. Uses Barcelona to hook into Apple's private iMessage frameworks on a Mac. Requires disabling SIP and AMFI to be able to hook into private frameworks.
  • Jailbroken iOS. Uses Brooklyn to hook into Apple's private iMessage frameworks on a jailbroken iOS device. 32-bit support has been deprecated, Barcelona can be used on newer iOS devices.
  • Additionally, there's android-sms, an Android app that implements the same IPC protocol as Brooklyn and Barcelona to bridge SMS from an Android phone.

You can find setup instructions for each of the connectors in the sidebar.

This bridge essentially has to be self-hosted, so you likely won't find public instances on normal Matrix servers. However, if you have a Beeper account, you can self-host the bridge using bbctl without self-hosting a Matrix server.

Discussion

Matrix room: #imessage:maunium.net

iMessage bridge setup (macOS)

Please note that everything in these docs are meant for server admins who want to self-host the bridge. If you're just looking to use the bridges, check out Beeper, which provides fully managed instances of all of these bridges.

Requirements

  • A computer running a reasonably new version of macOS.
    • The bridge requires full disk access in privacy settings to read your chat database.
  • A Matrix homeserver that supports application services (e.g. Synapse). You need access to register an appservice, which usually involves editing the homeserver config file.
  • A websocket proxy to receive appservice transactions.

If you want to compile the bridge manually (which is not required), you'll also need:

  • Go 1.19+ (download & installation instructions at https://go.dev/doc/install).
  • libolm3 with dev headers (brew install libolm).
  • Optionally libheif with dev headers for heif -> jpeg conversion (brew install libheif).

Installation

You may either compile the bridge manually or download a prebuilt executable from the mau.dev CI.

Compiling manually

  1. Clone the repo with git clone https://github.com/mautrix/imessage.git.
  2. Enter the directory (cd mautrix-imessage).
  3. Run ./build.sh to fetch Go dependencies and compile (build.sh will simply call go build with some additional flags).

Downloading a prebuilt executable

  1. Go to https://mau.dev/mautrix/imessage/-/pipelines?scope=branches&page=1
  2. Find the entry for the master branch and click the download button on the right-hand side in the list.
    • There are three entries: universal, arm64 (Apple Silicon) and amd64 (Intel). You can either pick universal, or the specific architecture depending on what Mac you have.
  3. Extract the downloaded zip file into a new directory.

Configuring and running

  1. Copy example-config.yaml to config.yaml
  2. Update the config to your liking.
    • You need to make sure that the address and domain field point to your homeserver.
    • You will also need to add your user ID to the bridge section.
  3. Generate the appservice registration file by running ./mautrix-imessage -g.
    • You can use the -c and -r flags to change the location of the config and registration files. They default to config.yaml and registration.yaml respectively.
  4. Set up mautrix-wsproxy.
  5. Update your registration file so the url field points to wsproxy (e.g. http://localhost:29331, this is where your homeserver reaches wsproxy), and make sure the websocket_proxy field in the bridge config also points to wsproxy (e.g. ws://matrix.example.com:29331, where the bridge reaches wsproxy).
  6. Register the bridge on your homeserver (see Registering appservices).
  7. Run the bridge with ./mautrix-imessage.
  8. If/when the bridge fails to initialize the iMessage connector with the operation not permitted error, go to System Preferences -> Security & Privacy -> Privacy -> Full Disk Access and grant access to the terminal you're running the bridge in.

iMessage bridge setup (macOS without SIP)

Note: Barcelona is no longer maintained and may not work on recent macOS versions. The normal Mac connector will likely work better.

Please note that everything in these docs are meant for server admins who want to self-host the bridge. If you're just looking to use the bridges, check out Beeper, which provides fully managed instances of all of these bridges.

Requirements

  • A computer running macOS 11 or higher, with SIP and AMFI disabled (see instructions below).
  • A Matrix homeserver that supports application services (e.g. Synapse). You need access to register an appservice, which usually involves editing the homeserver config file.
  • A websocket proxy to receive appservice transactions.

If you want to compile the bridge manually (which is not required), you'll also need:

  • Go 1.19+ (download & installation instructions at https://go.dev/doc/install).
  • libolm3 with dev headers (brew install libolm).
  • Optionally libheif with dev headers for heif -> jpeg conversion (brew install libheif).
  • Xcode 14.2+ if you want to build Barcelona yourself.

Installation

This form of the bridge consists of two components: Barcelona for connecting to iMessage and mautrix-imessage for connecting to Matrix. mautrix-imessage will run Barcelona as a subprocess and they communicate over stdio.

You may either compile the bridge and Barcelona manually, or download prebuilt executables from the mau.dev CI and GitHub actions respectively.

Because Barcelona hooks into Apple's private APIs, you must disable SIP (System Integrity Protection) and AMFI (Apple Mobile File Integrity) on the Mac for it to work. Disabling SIP will make your Mac less secure, so you should only do it on a Mac that you won't use for anything else.

Compiling manually

  1. Clone the repo with git clone https://github.com/mautrix/imessage.git.
  2. Enter the directory (cd mautrix-imessage).
  3. Run ./build.sh to fetch Go dependencies and compile (build.sh will simply call go build with some additional flags).
  4. Refer to the Barcelona build instructions for building Barcelona.

Downloading prebuilt executables

  1. Go to https://mau.dev/mautrix/imessage/-/pipelines?scope=branches&page=1
  2. Find the entry for the master branch and click the download button on the right-hand side in the list and choose "build universal".
  3. Extract the downloaded zip file into a new directory.
  4. Download darwin-barcelona-mautrix from https://github.com/beeper/barcelona/actions (latest direct download).

Disabling SIP and AMFI

Disabling SIP and AMFI will make your Mac significantly less secure.

  1. Boot into recovery mode (hold Command+R while booting) and open a terminal
  2. Run csrutil disable to disable SIP
  3. Run nvram boot-args="amfi_get_out_of_my_way=0x1" to disable AMFI
    • If you run other things like Electron apps on the Mac (which you definitely should not), you may want to add ipc_control_port_options=0 to prevent those from breaking.

If you are running macOS in a VM (e.g. through OSX-KVM) you may need to disable AMFI with a boot option. Assuming you are using OSX-KVM and OpenCore, open the config.plist (in EFI/OC/config.plist) and change the key boot-args to add amfi_get_out_of_my_way=0x1:

<key>boot-args</key>
<string>-v keepsyms=1 tlbto_us=0 vti=9 amfi_get_out_of_my_way=0x1</string>

You can also refer to Apple's official documentation on disabling SIP.

Configuring and running

  1. Follow steps 1-6 from the normal macOS setup
  2. Install Barcelona's com.apple.security.xpc.plist to /Library/Preferences/com.apple.security.xpc.plist
  3. In the imessage section of the config, change platform to mac-nosip and set imessage_rest_path to the path to the darwin-barcelona-mautrix executable you downloaded or compiled.
  4. Run the bridge with ./mautrix-imessage.

iMessage bridge setup (iOS)

DEPRECATED

mautrix-imessage has dropped support for 32-bit iOS devices, so this Brooklyn-based bridge setup is now deprecated. Newer iOS-based devices can use Barcelona (which is meant for macOS without SIP, but works on jailbroken iOS). However, instructions for Barcelona on iOS are not yet available here.

If you still want to use Brooklyn with an old iOS device, the last commit that supports Go 1.14 and 32-bit iOS is 31bc6e28


Please note that everything in these docs are meant for server admins who want to self-host the bridge. If you're just looking to use the bridges, check out Beeper, which provides fully managed instances of all of these bridges.

Requirements

  • A jailbroken iOS device, minimum and recommended is iPhone 4S with iOS 8.4(.1).
  • A Matrix homeserver that supports application services (e.g. Synapse). You need access to register an appservice, which usually involves editing the homeserver config file.
  • A websocket proxy to receive appservice transactions.

Installation

The bridge consists of two components: Brooklyn for connecting to iMessage and mautrix-imessage for connecting to Matrix. The Brooklyn app runs mautrix-imessage as a subprocess and they communicate over stdio.

The recommended way to install the app is getting a precompiled build from the Cydia repo. You can technically also compile everything yourself, but that is less documented.

Compiling manually

There are instructions for compiling Brooklyn in the GitHub repo.

Compiling mautrix-imessage for darwin/armv7 is more complicated and not currently documented. For more recent devices (i.e. armv8/arm64), it should be as simple as compiling mautrix-imessage on a Mac with Apple Silicon.

Precompiled builds

You can get the Brooklyn app with a bundled mautrix-imessage from the Cydia repo. The repo is currently available at http://maunium.mau.life/brooklyn/ (repo URL subject to change). After adding the repo in Cydia, simply install the "Brooklyn" package from the repo.

Configuring and running

  1. Get the example config and fill it out. You'll at least need to:
    • Fill everything in the homeserver section.
    • Set bridge -> user to your MXID.
    • Change imessage -> platform to ios.
    • Generate random tokens for the as_token and hs_token fields.
  2. Get the example registration and copy the relevant values from the config.
  3. Set up mautrix-wsproxy.
  4. Register the bridge on your homeserver (see Registering appservices).
  5. Serve the config file with the webserver of your choice. It's recommended to use a random file name or add HTTP basic auth to prevent other people from reading your config.
  6. Generate a QR code with the URL to your config (e.g. echo -n https://user:pass@example.com/your-config.yaml | qrencode -t ansiutf8).
  7. Scan the QR code with Brooklyn.

Troubleshooting

The brooklyn app keeps showing the QR Code reader popup

  1. Connect your iPhone to a Mac and open the Console app to see logs
  2. Filter for "Brooklyn" to see logs
  3. If you see Brooklyn mautrix-imessage sent error:Failed to download config: failed to open config.yaml for writing config: open config.yaml: permission denied, use the following to mitigate:
    1. install OpenSSH via Cydia
    2. ssh to the iPhone (find out IP via settings, user is root, password is alpine)
    3. the first ssh will take minutes because SSH keys are generated
    4. once you are ssh'ed into your phone, run chmod 777 /var/mobile/Documents/mautrix-imessage-armv7
    5. scan QR Code again
    6. check that the config file downloaded by running the following over SSH on your iPhone: cat /var/mobile/Documents/mautrix-imessage-armv7/config.yaml
  4. If you are seeing [ERROR] Error in appservice websocket: failed to open websocket: dial tcp [::1]:29331: connect: connection refused that means that your phone cannot talk to mautrix-wsproxy, make sure that the configured value for homeserver -> websocket_proxy can be resolved from your phone. You can check this by installing netcat in Cydia and then running the following over ssh: nc -zv <hostname> 29331 to see if it connects

SMS forwarding on iOS

If you're running the bridge on an old jailbroken iPhone, but want to keep your SIM card in your main iPhone, you can use iMessage's SMS forwarding feature to still send and receive text messages through the bridge.

By default, outgoing SMSes will only be forwarded on non-iPhone devices, and it'll just throw a no sim card installed error if you try to send an SMS. However, Brooklyn includes a bypass that can make the iPhone act like iPads and Macs and forward the SMS through another iPhone.

To enable the bypass:

  1. Deactivate iMessage (Settings -> Messages -> switch off "iMessage").
  2. Enable the bypass in Settings -> Brooklyn.
  3. Reset network settings (Settings -> General -> Reset -> Reset Network Settings).
    • The iPhone will forget your Wi-Fi settings and reboot.
  4. Set up your Wi-Fi connection again.
  5. Reactivate iMessage (same switch as step 1).

iMessage SMS bridge setup (Android)

This bridge is deprecated, the Google Messages bridge is recommended instead. However, it should still work to some extent if you follow the instructions carefully.

In addition to being an iMessage bridge, mautrix-imessage can run on Android to bridge SMS messages from your phone. The Android SMS bridge works similar to the jailbroken iOS setup, but instead of Brooklyn, the wrapper app for the bridge is android-sms.

Please note that everything in these docs are meant for server admins who want to self-host the bridge. If you're just looking to use the bridges, check out Beeper, which provides fully managed instances of all of these bridges.

Requirements

  • An Android device with Android 5 or higher.
  • A Matrix homeserver that supports application services (e.g. Synapse). You need access to register an appservice, which usually involves editing the homeserver config file.
  • A websocket proxy to receive appservice transactions. If you want end-to-bridge encryption, the sync proxy component (mentioned in the websocket proxy readme) is also recommended to minimize battery usage.

Installation

Compiling manually

  1. Install the latest Android SDK and NDK version 21.3.6528147.
  2. Clone the android-sms repo
    • The main branch doesn't currently work as a standalone app, so use the 0.1.89 tag.
    • Use --recursive when cloning or git submodule init && git submodule update after cloning to ensure that the mautrix-imessage submodule is present.
  3. Run ./mautrix.sh to compile mautrix-imessage for Android.
  4. Put your config.yaml in app/src/main/assets/ (create the directory if it doesn't exist).
  5. Run ./gradlew installDebug to compile the app and install it over ADB.

Precompiled builds

There are currently no precompiled versions available, as the config must be bundled at compile time. Support for setting up with QR code similar to the iOS setup will be added soon(™), and precompiled APKs will be available in the GitLab CI after that.

Configuring and running

  1. Get the example config and fill it out. You'll at least need to:
    • Fill everything in the homeserver section.
    • Set bridge -> user to your MXID.
    • Change imessage -> platform to android.
    • Generate random tokens for the as_token and hs_token fields.
    • The database and log directory paths must be absolute paths in the /data/user/0/com.beeper.sms.app directory.
  2. Get the example registration and copy the relevant values from the config.
  3. Set up mautrix-wsproxy (and the sync proxy).
  4. Add the path to the registration file to your Synapse homeserver.yaml under app_service_config_files, then restart Synapse.
  5. Build and run the android-sms app with your config.
  6. Open the app and grant it SMS permissions to start the bridge.

If something is wrong and you need to view the bridge logs, use logcat:

adb logcat --pid=$(adb shell ps | grep com.beeper.sms.app | awk '{ print $2 }')

(note that the pid will change if the app is restarted, so you'll have to re-run the command after restarts)