When you build a web application you need to email your users for password resets, orders placed and the like.
A trivial task, it seems. Here’s a one-liner in django:
<span>from</span> <span>django.core.mail</span> <span>import</span> <span>send_mail</span><span>send_mail</span><span>(</span><span>"</span><span>Password changed!</span><span>"</span><span>,</span><span>f</span><span>"</span><span>Hey </span><span>{</span><span>request</span><span>.</span><span>user</span><span>.</span><span>first_name</span><span>}</span><span>!</span><span>\n\n</span><span>Your password changed...</span><span>"</span><span>,</span><span>"</span><span>support@yourservice.com</span><span>"</span><span>,</span><span>[</span><span>request</span><span>.</span><span>user</span><span>.</span><span>email</span><span>],</span><span>fail_silently</span><span>=</span><span>False</span><span>)</span><span>from</span> <span>django.core.mail</span> <span>import</span> <span>send_mail</span> <span>send_mail</span><span>(</span><span>"</span><span>Password changed!</span><span>"</span><span>,</span> <span>f</span><span>"</span><span>Hey </span><span>{</span><span>request</span><span>.</span><span>user</span><span>.</span><span>first_name</span><span>}</span><span>!</span><span>\n\n</span><span>Your password changed...</span><span>"</span><span>,</span> <span>"</span><span>support@yourservice.com</span><span>"</span><span>,</span> <span>[</span><span>request</span><span>.</span><span>user</span><span>.</span><span>email</span><span>],</span> <span>fail_silently</span><span>=</span><span>False</span><span>)</span>from django.core.mail import send_mail send_mail("Password changed!", f"Hey {request.user.first_name}!\n\nYour password changed...", "support@yourservice.com", [request.user.email], fail_silently=False)
Enter fullscreen mode Exit fullscreen mode
Reality kicks in
A one-liner. Problem solved, now moving on.
But soon after, issues arise:
- You want to include some special characters like ” or ‘schön’. The one-liner no longer works.
- You need some conditional text for paying users. Which requires some templating logic.
- Some users fail to receive your notifications because their spam filter distrusts your bare-bones emails. Figure out what they need and build it.
- You want branded emails with your logo and stationery. You need to build complex MIME/multipart assemblies.
- You need to prevent delivery to actual users during development from your dev environment. You need environment-specific logic.
You can surely address each hurdle. Your one-liner grows into 10, 100, 1000 lines of code.
It may grow organically, adapted at every notification-sending point. Or structured – and get you to build your own DIY notification framework.
Notification system
You may build your DIY notification system, experiencing and overcoming a problem after the other.
But why go through the pain if the work is already done?
Tattler solves all the problems above and more. It’s a lightweight service you deploy within minutes, and your notification one-liners stay a one-liner:
<span>from</span> <span>tattler.client.tattler_py</span> <span>import</span> <span>send_notification</span><span># trigger notification in Python code </span><span>send_notification</span><span>(</span><span>'</span><span>website</span><span>'</span><span>,</span> <span>'</span><span>password_changed</span><span>'</span><span>,</span> <span>request</span><span>.</span><span>user</span><span>.</span><span>email</span><span>)</span><span>from</span> <span>tattler.client.tattler_py</span> <span>import</span> <span>send_notification</span> <span># trigger notification in Python code </span><span>send_notification</span><span>(</span><span>'</span><span>website</span><span>'</span><span>,</span> <span>'</span><span>password_changed</span><span>'</span><span>,</span> <span>request</span><span>.</span><span>user</span><span>.</span><span>email</span><span>)</span>from tattler.client.tattler_py import send_notification # trigger notification in Python code send_notification('website', 'password_changed', request.user.email)
Enter fullscreen mode Exit fullscreen mode
You can call tattler from any language and tech stack — with one call to its REST API:
<span># trigger notification via REST API in any language</span>curl <span>-XPOST</span> http://localhost:11503/notification/website/password_changed?user<span>=</span>foo@bar.com<span># trigger notification via REST API in any language</span> curl <span>-XPOST</span> http://localhost:11503/notification/website/password_changed?user<span>=</span>foo@bar.com# trigger notification via REST API in any language curl -XPOST http://localhost:11503/notification/website/password_changed?user=foo@bar.com
Enter fullscreen mode Exit fullscreen mode
Notification templates look like this:
Hey <span>{{</span> <span>user_firstname</span> <span>}}</span>!A quick heads up that your password was changed.If it was you, all good. If it wasn't, reply to this email without delay.<span>{%</span> <span>if</span> <span>user_account_type</span> <span>==</span> <span>'premium'</span> <span>%}</span>P.S.: Thank you for supporting us with your premium account! 🤩<span>{%</span> <span>endif</span> <span>%}</span>Hey <span>{{</span> <span>user_firstname</span> <span>}}</span>! A quick heads up that your password was changed. If it was you, all good. If it wasn't, reply to this email without delay. <span>{%</span> <span>if</span> <span>user_account_type</span> <span>==</span> <span>'premium'</span> <span>%}</span> P.S.: Thank you for supporting us with your premium account! 🤩 <span>{%</span> <span>endif</span> <span>%}</span>Hey {{ user_firstname }}! A quick heads up that your password was changed. If it was you, all good. If it wasn't, reply to this email without delay. {% if user_account_type == 'premium' %} P.S.: Thank you for supporting us with your premium account! 🤩 {% endif %}
Enter fullscreen mode Exit fullscreen mode
Deploying tattler
Install Tattler into an own folder ~/tattler_quickstart
, with this structure:
~/tattler_quickstart/├── conf/ # configuration files├── templates/ # templates for notifications└── venv/ # python virtual environment holding the code~/tattler_quickstart/ ├── conf/ # configuration files ├── templates/ # templates for notifications └── venv/ # python virtual environment holding the code~/tattler_quickstart/ ├── conf/ # configuration files ├── templates/ # templates for notifications └── venv/ # python virtual environment holding the code
Enter fullscreen mode Exit fullscreen mode
Here’s a terminal session performing the steps in this guide:
Installation
Create this directory structure:
<span># create the directory structure above</span><span>mkdir</span> <span>-p</span> ~/tattler_quickstart<span>cd</span> ~/tattler_quickstart<span>mkdir </span>conf templates<span># create and load a virtualenv to install into</span>python3 <span>-m</span> venv ~/tattler_quickstart/venv<span>.</span> ~/tattler_quickstart/venv/bin/activate<span># install tattler into it</span>pip <span>install </span>tattler<span># create the directory structure above</span> <span>mkdir</span> <span>-p</span> ~/tattler_quickstart <span>cd</span> ~/tattler_quickstart <span>mkdir </span>conf templates <span># create and load a virtualenv to install into</span> python3 <span>-m</span> venv ~/tattler_quickstart/venv <span>.</span> ~/tattler_quickstart/venv/bin/activate <span># install tattler into it</span> pip <span>install </span>tattler# create the directory structure above mkdir -p ~/tattler_quickstart cd ~/tattler_quickstart mkdir conf templates # create and load a virtualenv to install into python3 -m venv ~/tattler_quickstart/venv . ~/tattler_quickstart/venv/bin/activate # install tattler into it pip install tattler
Enter fullscreen mode Exit fullscreen mode
Configuration
Tattler’s configuration is organized in a bunch of files (envdir), whose filename is the configuration key and content its value. We’ll configure the following:
~/tattler_quickstart/└── conf/├── TATTLER_MASTER_MODE # actually deliver notifications├── TATTLER_SMTP_ADDRESS # IP:port of the SMTP server└── TATTLER_SMTP_AUTH # username:password SMTP credentials~/tattler_quickstart/ └── conf/ ├── TATTLER_MASTER_MODE # actually deliver notifications ├── TATTLER_SMTP_ADDRESS # IP:port of the SMTP server └── TATTLER_SMTP_AUTH # username:password SMTP credentials~/tattler_quickstart/ └── conf/ ├── TATTLER_MASTER_MODE # actually deliver notifications ├── TATTLER_SMTP_ADDRESS # IP:port of the SMTP server └── TATTLER_SMTP_AUTH # username:password SMTP credentials
Enter fullscreen mode Exit fullscreen mode
Let’s do it:
<span>cd</span> ~/tattler_quickstart/conf<span>echo</span> <span>'production'</span> <span>></span> TATTLER_MASTER_MODE<span># replace with your SMTP server</span><span>echo</span> <span>'127.0.0.1:25'</span> <span>></span> TATTLER_SMTP_ADDRESS<span>echo</span> <span>'username:password'</span> <span>></span> TATTLER_SMTP_AUTH<span>chmod </span>400 TATTLER_SMTP_AUTH<span>cd</span> ~/tattler_quickstart/conf <span>echo</span> <span>'production'</span> <span>></span> TATTLER_MASTER_MODE <span># replace with your SMTP server</span> <span>echo</span> <span>'127.0.0.1:25'</span> <span>></span> TATTLER_SMTP_ADDRESS <span>echo</span> <span>'username:password'</span> <span>></span> TATTLER_SMTP_AUTH <span>chmod </span>400 TATTLER_SMTP_AUTHcd ~/tattler_quickstart/conf echo 'production' > TATTLER_MASTER_MODE # replace with your SMTP server echo '127.0.0.1:25' > TATTLER_SMTP_ADDRESS echo 'username:password' > TATTLER_SMTP_AUTH chmod 400 TATTLER_SMTP_AUTH
Enter fullscreen mode Exit fullscreen mode
Running tattler
At this point tattler is ready to run, so let’s!
<span># run this from the terminal where you loaded your virtual environment</span>envdir ~/tattler_quickstart/conf tattler_server<span># run this from the terminal where you loaded your virtual environment</span> envdir ~/tattler_quickstart/conf tattler_server# run this from the terminal where you loaded your virtual environment envdir ~/tattler_quickstart/conf tattler_server
Enter fullscreen mode Exit fullscreen mode
And tattler will confirm:
INFO:tattler.server.pluginloader:Loading plugin PassThroughAddressbookPlugin (<class 'passthrough_addressbook_tattler_plugin.PassThroughAddressbookPlugin'>) from module passthrough_addressbook_tattler_pluginINFO:tattler.server.tattlersrv_http:Using templates from /../tattler_quickstart/templatesINFO:tattler.server.tattlersrv_http:==> Meet tattler @ https://tattler.dev . If you like tattler, consider posting about it! ;-)WARNING:tattler.server.tattlersrv_http:Tattler enterprise now serving at 127.0.0.1:11503INFO:tattler.server.pluginloader:Loading plugin PassThroughAddressbookPlugin (<class 'passthrough_addressbook_tattler_plugin.PassThroughAddressbookPlugin'>) from module passthrough_addressbook_tattler_plugin INFO:tattler.server.tattlersrv_http:Using templates from /../tattler_quickstart/templates INFO:tattler.server.tattlersrv_http:==> Meet tattler @ https://tattler.dev . If you like tattler, consider posting about it! ;-) WARNING:tattler.server.tattlersrv_http:Tattler enterprise now serving at 127.0.0.1:11503INFO:tattler.server.pluginloader:Loading plugin PassThroughAddressbookPlugin (<class 'passthrough_addressbook_tattler_plugin.PassThroughAddressbookPlugin'>) from module passthrough_addressbook_tattler_plugin INFO:tattler.server.tattlersrv_http:Using templates from /../tattler_quickstart/templates INFO:tattler.server.tattlersrv_http:==> Meet tattler @ https://tattler.dev . If you like tattler, consider posting about it! ;-) WARNING:tattler.server.tattlersrv_http:Tattler enterprise now serving at 127.0.0.1:11503
Enter fullscreen mode Exit fullscreen mode
Done!
Sending a test notification
Ask tattler to send a test notification with the tattler_notify
command:
<span># [ recipient ] [ scope ] [ event ] [ do send! ]</span>tattler_notify my@email.com demoscope demoevent <span>-m</span> production<span># [ recipient ] [ scope ] [ event ] [ do send! ]</span> tattler_notify my@email.com demoscope demoevent <span>-m</span> production# [ recipient ] [ scope ] [ event ] [ do send! ] tattler_notify my@email.com demoscope demoevent -m production
Enter fullscreen mode Exit fullscreen mode
This sends a demo notification embedded in tattler, so you don’t need to write your own content. -m production
tells tattler to deliver to the actual recipient my@email.com
. Without it, tattler will safely operate in development mode and divert any delivery to a development address (configured in TATTLER_DEBUG_RECIPIENT_EMAIL
) so real users aren’t accidentally sent emails during development.
Proceed to your mailbox to find the result. If nothing is in, check the logs of tattler_server
. They should look like this:
[...]INFO:tattler.server.sendable.vector_email:SMTP delivery to 127.0.0.1:25 completed successfully.INFO:tattler.server.tattlersrv_http:Notification sent. [{'id': 'email:b386e396-7ad4-4d50-bc2c-1406bf6a8814', 'vector': 'email', 'resultCode': 0, 'result': 'success', 'detail': 'OK'}][...] INFO:tattler.server.sendable.vector_email:SMTP delivery to 127.0.0.1:25 completed successfully. INFO:tattler.server.tattlersrv_http:Notification sent. [{'id': 'email:b386e396-7ad4-4d50-bc2c-1406bf6a8814', 'vector': 'email', 'resultCode': 0, 'result': 'success', 'detail': 'OK'}][...] INFO:tattler.server.sendable.vector_email:SMTP delivery to 127.0.0.1:25 completed successfully. INFO:tattler.server.tattlersrv_http:Notification sent. [{'id': 'email:b386e396-7ad4-4d50-bc2c-1406bf6a8814', 'vector': 'email', 'resultCode': 0, 'result': 'success', 'detail': 'OK'}]
Enter fullscreen mode Exit fullscreen mode
If they don’t, you’ll see an error description. Most likely your SMTP server is misconfigured, so check again Configuration and restart tattler_server
when fixed.
Next, you’ll want to write your own content to notify users.
Templates
Write your own template to define what content to send upon an event like “password_changed”.
Templates are folders organized in this directory structure:
~/tattler_quickstart/└── templates/ # base directory holding all notification templates└── website/ # an arbitrary 'scope' (ignore for now)├── password_changed/ # template to send when user changes password├── order_placed/ # template to send when user places an order└── ...~/tattler_quickstart/ └── templates/ # base directory holding all notification templates └── website/ # an arbitrary 'scope' (ignore for now) ├── password_changed/ # template to send when user changes password ├── order_placed/ # template to send when user places an order └── ...~/tattler_quickstart/ └── templates/ # base directory holding all notification templates └── website/ # an arbitrary 'scope' (ignore for now) ├── password_changed/ # template to send when user changes password ├── order_placed/ # template to send when user places an order └── ...
Enter fullscreen mode Exit fullscreen mode
What does the content of the event template itself look like?
password_changed/ # arbitrary event name for the template└── email/├── body.html # template to expand for the HTML body├── body.txt # template to expand for the plain text body└── subject.txt # template to expand for the subjectpassword_changed/ # arbitrary event name for the template └── email/ ├── body.html # template to expand for the HTML body ├── body.txt # template to expand for the plain text body └── subject.txt # template to expand for the subjectpassword_changed/ # arbitrary event name for the template └── email/ ├── body.html # template to expand for the HTML body ├── body.txt # template to expand for the plain text body └── subject.txt # template to expand for the subject
Enter fullscreen mode Exit fullscreen mode
Let’s proceed to create the directories for one:
<span>cd</span> ~/tattler_quickstart<span>mkdir</span> <span>-p</span> templates/password_changed/email<span>cd</span> ~/tattler_quickstart <span>mkdir</span> <span>-p</span> templates/password_changed/emailcd ~/tattler_quickstart mkdir -p templates/password_changed/email
Enter fullscreen mode Exit fullscreen mode
Plain text template
Now, edit file email/body.txt
with the content of the plain text email. This is seen by users of webmails or email applications that lack support for HTML emails.
<span>{# file email/body.txt (this is a comment) #}</span>Hey <span>{{</span> <span>user_firstname</span> <span>}}</span>!A quick heads up that your password was changed.If it was you, all good. If it wasn't, reply to this email without delay.<span>{%</span> <span>if</span> <span>user_account_type</span> <span>==</span> <span>'premium'</span> <span>%}</span>P.S.: Thank you for supporting us with your premium account! 🤩<span>{%</span> <span>endif</span> <span>%}</span><span>{# file email/body.txt (this is a comment) #}</span> Hey <span>{{</span> <span>user_firstname</span> <span>}}</span>! A quick heads up that your password was changed. If it was you, all good. If it wasn't, reply to this email without delay. <span>{%</span> <span>if</span> <span>user_account_type</span> <span>==</span> <span>'premium'</span> <span>%}</span> P.S.: Thank you for supporting us with your premium account! 🤩 <span>{%</span> <span>endif</span> <span>%}</span>{# file email/body.txt (this is a comment) #} Hey {{ user_firstname }}! A quick heads up that your password was changed. If it was you, all good. If it wasn't, reply to this email without delay. {% if user_account_type == 'premium' %} P.S.: Thank you for supporting us with your premium account! 🤩 {% endif %}
Enter fullscreen mode Exit fullscreen mode
Subject
Next, edit file email/subject.txt
with the subject of the email:
Warning: password changed for account <span>{{</span> <span>user_email</span> <span>}}</span>! ️Warning: password changed for account <span>{{</span> <span>user_email</span> <span>}}</span>! ️Warning: password changed for account {{ user_email }}! ️
Enter fullscreen mode Exit fullscreen mode
Notice that the subject supports both non-ASCII characters and templating too!
HTML template
Then, edit file email/body.html
with HTML content. Here’s where you implement your company’s stationery — colors, logo, fonts, layout and all.
Make sure to stick to HTML constructs supported by email clients! That’s MUCH less than your browser does. See caniemail.com for help.
<span>{# file email/body.html (this is a comment) #}</span><span><html></span><span><head><title></span>Your password was changed!<span></title></head></span><span><body></span><span><h1></span>Dear <span>{{</span> <span>user_firstname</span> <span>}}</span>! <span></h1></span><span><p></span>A quick heads up that <span><strong></span>your password was changed<span></strong></span>!<span></p></span><span><p></span>If it was you, all good. If it wasn't, reply to this email without delay.<span></p></span><span>{%</span> <span>if</span> <span>user_account_type</span> <span>==</span> <span>'premium'</span> <span>%}</span><span><p</span> <span>style=</span><span>"color: darkgray"</span><span>></span>P.S.: Thank you for supporting us with your premium account! 🤩<span></p></span><span>{%</span> <span>endif</span> <span>%}</span><span></body></span><span></html></span><span>{# file email/body.html (this is a comment) #}</span> <span><html></span> <span><head><title></span>Your password was changed!<span></title></head></span> <span><body></span> <span><h1></span>Dear <span>{{</span> <span>user_firstname</span> <span>}}</span>! <span></h1></span> <span><p></span>A quick heads up that <span><strong></span>your password was changed<span></strong></span>!<span></p></span> <span><p></span>If it was you, all good. If it wasn't, reply to this email without delay.<span></p></span> <span>{%</span> <span>if</span> <span>user_account_type</span> <span>==</span> <span>'premium'</span> <span>%}</span> <span><p</span> <span>style=</span><span>"color: darkgray"</span><span>></span>P.S.: Thank you for supporting us with your premium account! 🤩<span></p></span> <span>{%</span> <span>endif</span> <span>%}</span> <span></body></span> <span></html></span>{# file email/body.html (this is a comment) #} <html> <head><title>Your password was changed!</title></head> <body> <h1>Dear {{ user_firstname }}! </h1> <p>A quick heads up that <strong>your password was changed</strong>!</p> <p>If it was you, all good. If it wasn't, reply to this email without delay.</p> {% if user_account_type == 'premium' %} <p style="color: darkgray">P.S.: Thank you for supporting us with your premium account! 🤩</p> {% endif %} </body> </html>
Enter fullscreen mode Exit fullscreen mode
Send your template
Now that you’ve got your own template, tell tattler to deliver it:
<span># [ recipient ] [ scope ] [ event ] [ do send! ]</span>tattler_notify my@email.com website password_changed <span>-m</span> production<span># [ recipient ] [ scope ] [ event ] [ do send! ]</span> tattler_notify my@email.com website password_changed <span>-m</span> production# [ recipient ] [ scope ] [ event ] [ do send! ] tattler_notify my@email.com website password_changed -m production
Enter fullscreen mode Exit fullscreen mode
Check your inbox and that’s it!
Integration
Now that tattler runs and you provisioned your template, how to have it sent from your code?
Whenever your application wants to notify an event, like “password changed”, you can fire it either:
- From python via tattler’s native client API.
- From any other language via tattler’s REST API.
Let’s look at some concrete examples.
Python
Tattler is written in python, so python developers can use its native python API:
<span>from</span> <span>tattler.client.tattler_py</span> <span>import</span> <span>send_notification</span><span>send_notification</span><span>(</span><span>'</span><span>website</span><span>'</span><span>,</span> <span>'</span><span>password_changed</span><span>'</span><span>,</span> <span>request</span><span>.</span><span>user</span><span>.</span><span>email</span><span>)</span><span>from</span> <span>tattler.client.tattler_py</span> <span>import</span> <span>send_notification</span> <span>send_notification</span><span>(</span><span>'</span><span>website</span><span>'</span><span>,</span> <span>'</span><span>password_changed</span><span>'</span><span>,</span> <span>request</span><span>.</span><span>user</span><span>.</span><span>email</span><span>)</span>from tattler.client.tattler_py import send_notification send_notification('website', 'password_changed', request.user.email)
Enter fullscreen mode Exit fullscreen mode
REST API
This works with any language and tech stack. Simply make a POST request to 127.0.0.1:80
http://localhost:11503/notification/website/password_changed?user=foo@bar.com[ API base URL ] [scope] [event name] [ recipient ]http://localhost:11503/notification/website/password_changed?user=foo@bar.com [ API base URL ] [scope] [event name] [ recipient ]http://localhost:11503/notification/website/password_changed?user=foo@bar.com [ API base URL ] [scope] [event name] [ recipient ]
Enter fullscreen mode Exit fullscreen mode
Notice the following:
- Host
localhost
and TCP port11503
- Base URL of the API =
http://localhost:11503/notification/
- POST request (not GET)!
-
website
is the scope name (see Templates) -
password_changed
is the template name (see Templates) -
user=foo@bar.com
tells tattler whom to send to
Here’s how to leverage this from a number of programming languages.
Go
<span>package</span> <span>main</span><span>import</span> <span>(</span><span>"bytes"</span><span>"net/http"</span><span>)</span><span>func</span> <span>main</span><span>()</span> <span>{</span><span>_</span><span>,</span> <span>err</span> <span>:=</span> <span>http</span><span>.</span><span>NewRequest</span><span>(</span><span>"POST"</span><span>,</span> <span>"http://localhost:11503/notification/website/password_changed?user=my@email.com"</span><span>,</span> <span>bytes</span><span>.</span><span>NewBufferString</span><span>(</span><span>""</span><span>))</span><span>if</span> <span>err</span> <span>!=</span> <span>nil</span> <span>{</span><span>panic</span><span>(</span><span>err</span><span>)</span><span>}</span><span>}</span><span>package</span> <span>main</span> <span>import</span> <span>(</span> <span>"bytes"</span> <span>"net/http"</span> <span>)</span> <span>func</span> <span>main</span><span>()</span> <span>{</span> <span>_</span><span>,</span> <span>err</span> <span>:=</span> <span>http</span><span>.</span><span>NewRequest</span><span>(</span><span>"POST"</span><span>,</span> <span>"http://localhost:11503/notification/website/password_changed?user=my@email.com"</span><span>,</span> <span>bytes</span><span>.</span><span>NewBufferString</span><span>(</span><span>""</span><span>))</span> <span>if</span> <span>err</span> <span>!=</span> <span>nil</span> <span>{</span> <span>panic</span><span>(</span><span>err</span><span>)</span> <span>}</span> <span>}</span>package main import ( "bytes" "net/http" ) func main() { _, err := http.NewRequest("POST", "http://localhost:11503/notification/website/password_changed?user=my@email.com", bytes.NewBufferString("")) if err != nil { panic(err) } }
Enter fullscreen mode Exit fullscreen mode
C
<span>using</span> <span>System.Net.Http</span><span>;</span><span>public</span> <span>class</span> <span>Program</span><span>{</span><span>public</span> <span>static</span> <span>void</span> <span>Main</span><span>(</span><span>string</span><span>[]</span> <span>args</span><span>)</span><span>{</span><span>using</span> <span>var</span> <span>client</span> <span>=</span> <span>new</span> <span>HttpClient</span><span>();</span><span>var</span> <span>response</span> <span>=</span> <span>await</span> <span>client</span><span>.</span><span>PostAsync</span><span>(</span><span>"http://localhost:11503/notification/website/password_changed?user=my@email.com"</span><span>,</span> <span>null</span><span>);</span><span>}</span><span>}</span><span>using</span> <span>System.Net.Http</span><span>;</span> <span>public</span> <span>class</span> <span>Program</span> <span>{</span> <span>public</span> <span>static</span> <span>void</span> <span>Main</span><span>(</span><span>string</span><span>[]</span> <span>args</span><span>)</span> <span>{</span> <span>using</span> <span>var</span> <span>client</span> <span>=</span> <span>new</span> <span>HttpClient</span><span>();</span> <span>var</span> <span>response</span> <span>=</span> <span>await</span> <span>client</span><span>.</span><span>PostAsync</span><span>(</span><span>"http://localhost:11503/notification/website/password_changed?user=my@email.com"</span><span>,</span> <span>null</span><span>);</span> <span>}</span> <span>}</span>using System.Net.Http; public class Program { public static void Main(string[] args) { using var client = new HttpClient(); var response = await client.PostAsync("http://localhost:11503/notification/website/password_changed?user=my@email.com", null); } }
Enter fullscreen mode Exit fullscreen mode
Java
<span>import</span> <span>java.net.URI</span><span>;</span><span>import</span> <span>java.net.http.HttpClient</span><span>;</span><span>import</span> <span>java.net.http.HttpRequest</span><span>;</span><span>public</span> <span>class</span> <span>App</span> <span>{</span><span>public</span> <span>static</span> <span>void</span> <span>main</span><span>(</span><span>String</span><span>[]</span> <span>args</span><span>)</span> <span>{</span><span>HttpClient</span> <span>client</span> <span>=</span> <span>HttpClient</span><span>.</span><span>newHttpClient</span><span>();</span><span>HttpRequest</span> <span>request</span> <span>=</span> <span>HttpRequest</span><span>.</span><span>newBuilder</span><span>()</span><span>.</span><span>uri</span><span>(</span><span>new</span> <span>URI</span><span>(</span><span>"http://localhost:11503/notification/website/password_changed?user=my@email.com"</span><span>))</span><span>.</span><span>header</span><span>(</span><span>"Content-Type"</span><span>,</span> <span>"application/json"</span><span>)</span><span>.</span><span>POST</span><span>(</span><span>HttpRequest</span><span>.</span><span>BodyPublishers</span><span>.</span><span>noBody</span><span>())</span><span>.</span><span>build</span><span>();</span><span>HttpResponse</span><span><</span><span>String</span><span>></span> <span>response</span> <span>=</span> <span>client</span><span>.</span><span>send</span><span>(</span><span>request</span><span>,</span> <span>HttpResponse</span><span>.</span><span>BodyHandlers</span><span>.</span><span>ofString</span><span>());</span><span>}</span><span>}</span><span>import</span> <span>java.net.URI</span><span>;</span> <span>import</span> <span>java.net.http.HttpClient</span><span>;</span> <span>import</span> <span>java.net.http.HttpRequest</span><span>;</span> <span>public</span> <span>class</span> <span>App</span> <span>{</span> <span>public</span> <span>static</span> <span>void</span> <span>main</span><span>(</span><span>String</span><span>[]</span> <span>args</span><span>)</span> <span>{</span> <span>HttpClient</span> <span>client</span> <span>=</span> <span>HttpClient</span><span>.</span><span>newHttpClient</span><span>();</span> <span>HttpRequest</span> <span>request</span> <span>=</span> <span>HttpRequest</span><span>.</span><span>newBuilder</span><span>()</span> <span>.</span><span>uri</span><span>(</span><span>new</span> <span>URI</span><span>(</span><span>"http://localhost:11503/notification/website/password_changed?user=my@email.com"</span><span>))</span> <span>.</span><span>header</span><span>(</span><span>"Content-Type"</span><span>,</span> <span>"application/json"</span><span>)</span> <span>.</span><span>POST</span><span>(</span><span>HttpRequest</span><span>.</span><span>BodyPublishers</span><span>.</span><span>noBody</span><span>())</span> <span>.</span><span>build</span><span>();</span> <span>HttpResponse</span><span><</span><span>String</span><span>></span> <span>response</span> <span>=</span> <span>client</span><span>.</span><span>send</span><span>(</span><span>request</span><span>,</span> <span>HttpResponse</span><span>.</span><span>BodyHandlers</span><span>.</span><span>ofString</span><span>());</span> <span>}</span> <span>}</span>import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; public class App { public static void main(String[] args) { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(new URI("http://localhost:11503/notification/website/password_changed?user=my@email.com")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.noBody()) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); } }
Enter fullscreen mode Exit fullscreen mode
Swift
<span>import</span> <span>Foundation</span><span>let</span> <span>url</span> <span>=</span> <span>URL</span><span>(</span><span>string</span><span>:</span> <span>"http://localhost:11503/notification/website/password_changed?user=my@email.com"</span><span>)</span><span>!</span><span>var</span> <span>request</span> <span>=</span> <span>URLRequest</span><span>(</span><span>url</span><span>:</span> <span>url</span><span>)</span><span>request</span><span>.</span><span>httpMethod</span> <span>=</span> <span>"POST"</span><span>request</span><span>.</span><span>setValue</span><span>(</span><span>"application/json"</span><span>,</span> <span>forHTTPHeaderField</span><span>:</span> <span>"Content-Type"</span><span>)</span><span>request</span><span>.</span><span>httpBody</span> <span>=</span> <span>Data</span><span>()</span><span>let</span> <span>task</span> <span>=</span> <span>URLSession</span><span>.</span><span>shared</span><span>.</span><span>dataTask</span><span>(</span><span>with</span><span>:</span> <span>request</span><span>)</span> <span>{</span> <span>data</span><span>,</span> <span>response</span><span>,</span> <span>error</span> <span>in</span><span>if</span> <span>let</span> <span>httpResponse</span> <span>=</span> <span>response</span> <span>as?</span> <span>HTTPURLResponse</span> <span>{</span><span>print</span><span>(</span><span>"Response Code: </span><span>\(</span><span>httpResponse</span><span>.</span><span>statusCode</span><span>)</span><span>"</span><span>)</span><span>}</span><span>}</span><span>task</span><span>.</span><span>resume</span><span>()</span><span>import</span> <span>Foundation</span> <span>let</span> <span>url</span> <span>=</span> <span>URL</span><span>(</span><span>string</span><span>:</span> <span>"http://localhost:11503/notification/website/password_changed?user=my@email.com"</span><span>)</span><span>!</span> <span>var</span> <span>request</span> <span>=</span> <span>URLRequest</span><span>(</span><span>url</span><span>:</span> <span>url</span><span>)</span> <span>request</span><span>.</span><span>httpMethod</span> <span>=</span> <span>"POST"</span> <span>request</span><span>.</span><span>setValue</span><span>(</span><span>"application/json"</span><span>,</span> <span>forHTTPHeaderField</span><span>:</span> <span>"Content-Type"</span><span>)</span> <span>request</span><span>.</span><span>httpBody</span> <span>=</span> <span>Data</span><span>()</span> <span>let</span> <span>task</span> <span>=</span> <span>URLSession</span><span>.</span><span>shared</span><span>.</span><span>dataTask</span><span>(</span><span>with</span><span>:</span> <span>request</span><span>)</span> <span>{</span> <span>data</span><span>,</span> <span>response</span><span>,</span> <span>error</span> <span>in</span> <span>if</span> <span>let</span> <span>httpResponse</span> <span>=</span> <span>response</span> <span>as?</span> <span>HTTPURLResponse</span> <span>{</span> <span>print</span><span>(</span><span>"Response Code: </span><span>\(</span><span>httpResponse</span><span>.</span><span>statusCode</span><span>)</span><span>"</span><span>)</span> <span>}</span> <span>}</span> <span>task</span><span>.</span><span>resume</span><span>()</span>import Foundation let url = URL(string: "http://localhost:11503/notification/website/password_changed?user=my@email.com")! var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = Data() let task = URLSession.shared.dataTask(with: request) { data, response, error in if let httpResponse = response as? HTTPURLResponse { print("Response Code: \(httpResponse.statusCode)") } } task.resume()
Enter fullscreen mode Exit fullscreen mode
Conclusion
That’s it. You installed and tested tattler within minutes, and integrated it into your code within a few more minutes.
As your needs grow, tattler smoothly accommodates them without bloating your codebase.
Advanced topics include passing variables to templates, sending SMS, having tattler plug-ins automatically retrieve user addresses or template variables. Find it all in tattler’s thorough documentation.
Tattler is proudly open-source, with a vastly liberal license (BSD) allowing commercial use. And if your company’s policies require support and maintenance for every dependency, tattler offers them in an enterprise subscription, plus more features like S/MIME, multilingualism and delivery with Telegram and WhatsApp.
暂无评论内容