PingPong
PingPong is a small Elixir notification library for sending messages through multiple services with one consistent API.
Built-in services:
:discord- sends messages to a Discord webhook:telegram- sends messages through a Telegram bot:mock- local test service that replies to%{message: "Ping!"}
Installation
Add ping_pong to your dependencies:
def deps do
[
{:ping_pong, "~> 0.1.0"}
]
end
Then fetch dependencies:
mix deps.get
PingPong starts a Task.Supervisor with the application, so async delivery
works when the OTP application is running.
Quick Start
Send a notification synchronously:
PingPong.send(:mock, %{message: "Ping!"}, %{})
Successful calls return {:ok, response}. Failed calls return
{:error, {reason, detail}} or a service-specific error tuple.
Discord
Discord messages use a webhook URL.
PingPong.send(
:discord,
%{content: "Deploy finished successfully"},
%{webhook: "https://discord.com/api/webhooks/WEBHOOK_ID/WEBHOOK_TOKEN"}
)
Required payload:
%{content: "Message text"}
Required options:
%{webhook: "Discord webhook URL"}
See guides/discord.md for setup steps and examples.
Telegram
Telegram messages use a bot token and a chat ID.
PingPong.send(
:telegram,
%{content: "Deploy finished successfully", chat_id: "123456789"},
%{token: "123456:telegram-bot-token"}
)
Required payload:
%{
content: "Message text",
chat_id: "Telegram chat ID"
}
Required options:
%{token: "Telegram bot token"}
See guides/telegram.md for bot setup and examples.
Async Delivery
Use send_async/3 when you want a task instead of blocking on the service
response.
{:ok, task} =
PingPong.send_async(
:mock,
%{message: "Ping!"},
%{}
)
Task.await(task)
Unknown services still return immediately:
PingPong.send_async(:unknown, %{}, %{})
#=> {:error, {:unknown_service, :unknown}}
Multiple Notifications
Use send_multiple/1 to send a list of named notifications. Each notification
is a {service, payload, options} tuple.
notifications = [
ops: {:discord, %{content: "Build passed"}, %{webhook: discord_webhook}},
team: {:telegram, %{content: "Build passed", chat_id: chat_id}, %{token: bot_token}}
]
PingPong.send_multiple(notifications)
The return value preserves the IDs:
[
ops: {:ok, ""},
team: {:ok, %{"ok" => true}}
]
Error Handling
Common errors:
{:error, {:unknown_service, service}}
{:error, {:missing_required_params}, nil}
{:error, {:error_response, response}}
{:error, {:error, reason}}
For production apps, handle both success and error tuples explicitly:
case PingPong.send(:discord, payload, options) do
{:ok, response} ->
{:ok, response}
{:error, reason} ->
Logger.warning("Notification failed: #{inspect(reason)}")
{:error, reason}
end
Custom Services
Custom services implement PingPong.ServiceBehaviour and can be registered in
application config.
defmodule MyApp.EmailService do
@behaviour PingPong.ServiceBehaviour
@impl true
def call(payload, options) do
# deliver notification here
{:ok, %{payload: payload, options: options}}
end
end
Register the service:
config :ping_pong,
services: [
email: MyApp.EmailService
]
Then send with the custom service key:
PingPong.send(:email, %{subject: "Hello"}, %{to: "team@example.com"})
Development
Run the test suite:
mix test
Format code:
mix format
Contributing
Contributions are welcome. A good contribution usually starts with a small, focused issue or pull request.
Before opening a pull request:
- Fork the repository.
- Create a feature branch.
- Add or update tests for your change.
- Run the test suite with
mix test. - Format the code with
mix format. - Describe what changed and why in the pull request.
For new notification services, implement PingPong.ServiceBehaviour, add the
service to the registry or configuration examples, and include tests for
success, validation errors, and HTTP/API failures.
Support
If PingPong helps you, you can support the project here:
License
PingPong is released under the MIT License. See LICENSE for details.