The right way to email your users

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:

  1. You want to include some special characters like ” or ‘schön’. The one-liner no longer works.
  2. You need some conditional text for paying users. Which requires some templating logic.
  3. Some users fail to receive your notifications because their spam filter distrusts your bare-bones emails. Figure out what they need and build it.
  4. You want branded emails with your logo and stationery. You need to build complex MIME/multipart assemblies.
  5. 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_AUTH
cd ~/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_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
INFO: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
INFO: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 subject
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 subject
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 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/email
cd ~/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 port 11503
  • 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.

原文链接:The right way to email your users

© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享
When you procrastinate, you become a slave to yesterday.
拖延会让你成为昨天的奴隶
评论 抢沙发

请登录后发表评论

    暂无评论内容