Merge branch 'feature/Ability-Send-Client-Email'
This commit is contained in:
		
						commit
						0981c2fe07
					
				| 
						 | 
				
			
			@ -29,6 +29,9 @@ function renderClientList(data) {
 | 
			
		|||
                                <div class="btn-group">
 | 
			
		||||
                                    <button onclick="location.href='/download?clientid=${obj.Client.id}'" type="button"
 | 
			
		||||
                                        class="btn btn-outline-success btn-sm">Download</button>
 | 
			
		||||
                                    <button type="button" class="btn btn-outline-warning btn-sm" data-toggle="modal"
 | 
			
		||||
                                        data-target="#modal_email_client" data-clientid="${obj.Client.id}"
 | 
			
		||||
                                        data-clientname="${obj.Client.name}">Email</button>
 | 
			
		||||
                                    <button type="button" class="btn btn-outline-primary btn-sm" data-toggle="modal"
 | 
			
		||||
                                        data-target="#modal_edit_client" data-clientid="${obj.Client.id}"
 | 
			
		||||
                                        data-clientname="${obj.Client.name}">Edit</button>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,8 +2,13 @@ version: '3'
 | 
			
		|||
 | 
			
		||||
services:
 | 
			
		||||
  wg:
 | 
			
		||||
    image: ngoduykhanh/wireguard-ui:latest
 | 
			
		||||
    build: .
 | 
			
		||||
    #image: ngoduykhanh/wireguard-ui:latest
 | 
			
		||||
    container_name: wgui
 | 
			
		||||
    environment:
 | 
			
		||||
      - SENDGRID_API_KEY
 | 
			
		||||
      - EMAIL_FROM
 | 
			
		||||
      - EMAIL_FROM_NAME
 | 
			
		||||
    ports:
 | 
			
		||||
      - 5000:5000
 | 
			
		||||
    logging:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
package emailer
 | 
			
		||||
 | 
			
		||||
type Attachment struct {
 | 
			
		||||
	Name string
 | 
			
		||||
	Data []byte
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Emailer interface {
 | 
			
		||||
	Send(toName string, to string, subject string, content string, attachments []Attachment) error
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,54 @@
 | 
			
		|||
package emailer
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
 | 
			
		||||
	"github.com/sendgrid/sendgrid-go"
 | 
			
		||||
	"github.com/sendgrid/sendgrid-go/helpers/mail"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type SendgridApiMail struct {
 | 
			
		||||
	apiKey   string
 | 
			
		||||
	fromName string
 | 
			
		||||
	from     string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewSendgridApiMail(apiKey, fromName, from string) *SendgridApiMail {
 | 
			
		||||
	ans := SendgridApiMail{apiKey: apiKey, fromName: fromName, from: from}
 | 
			
		||||
	return &ans
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *SendgridApiMail) Send(toName string, to string, subject string, content string, attachments []Attachment) error {
 | 
			
		||||
	m := mail.NewV3Mail()
 | 
			
		||||
 | 
			
		||||
	mailFrom := mail.NewEmail(o.fromName, o.from)
 | 
			
		||||
	mailContent := mail.NewContent("text/html", content)
 | 
			
		||||
	mailTo := mail.NewEmail(toName, to)
 | 
			
		||||
 | 
			
		||||
	m.SetFrom(mailFrom)
 | 
			
		||||
	m.AddContent(mailContent)
 | 
			
		||||
 | 
			
		||||
	personalization := mail.NewPersonalization()
 | 
			
		||||
	personalization.AddTos(mailTo)
 | 
			
		||||
	personalization.Subject = subject
 | 
			
		||||
 | 
			
		||||
	m.AddPersonalizations(personalization)
 | 
			
		||||
 | 
			
		||||
	toAdd := make([]*mail.Attachment, 0, len(attachments))
 | 
			
		||||
	for i := range attachments {
 | 
			
		||||
		var att mail.Attachment
 | 
			
		||||
		encoded := base64.StdEncoding.EncodeToString(attachments[i].Data)
 | 
			
		||||
		att.SetContent(encoded)
 | 
			
		||||
		att.SetType("text/plain")
 | 
			
		||||
		att.SetFilename(attachments[i].Name)
 | 
			
		||||
		att.SetDisposition("attachment")
 | 
			
		||||
		toAdd = append(toAdd, &att)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m.AddAttachment(toAdd...)
 | 
			
		||||
	request := sendgrid.GetRequest(o.apiKey, "/v3/mail/send", "https://api.sendgrid.com")
 | 
			
		||||
	request.Method = "POST"
 | 
			
		||||
	request.Body = mail.GetRequestBody(m)
 | 
			
		||||
	_, err := sendgrid.API(request)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										2
									
								
								go.mod
								
								
								
								
							| 
						 | 
				
			
			@ -14,6 +14,8 @@ require (
 | 
			
		|||
	github.com/leodido/go-urn v1.2.0 // indirect
 | 
			
		||||
	github.com/rs/xid v1.2.1
 | 
			
		||||
	github.com/sdomino/scribble v0.0.0-20191024200645-4116320640ba
 | 
			
		||||
	github.com/sendgrid/rest v2.6.4+incompatible // indirect
 | 
			
		||||
	github.com/sendgrid/sendgrid-go v3.10.0+incompatible
 | 
			
		||||
	github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086
 | 
			
		||||
	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200324154536-ceff61240acf
 | 
			
		||||
	gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										4
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										4
									
								
								go.sum
								
								
								
								
							| 
						 | 
				
			
			@ -103,6 +103,10 @@ github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
 | 
			
		|||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
 | 
			
		||||
github.com/sdomino/scribble v0.0.0-20191024200645-4116320640ba h1:8QAc9wFAf2b/9cAXskm0wBylObZ0bTpRcaP7ThjLPVQ=
 | 
			
		||||
github.com/sdomino/scribble v0.0.0-20191024200645-4116320640ba/go.mod h1:W6zxGUBCXRR5QugSd/nFcFVmwoGnvpjiNY/JwT03Wew=
 | 
			
		||||
github.com/sendgrid/rest v2.6.4+incompatible h1:lq6gAQxLwVBf3mVyCCSHI6mgF+NfaJFJHjT0kl6SSo8=
 | 
			
		||||
github.com/sendgrid/rest v2.6.4+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
 | 
			
		||||
github.com/sendgrid/sendgrid-go v3.10.0+incompatible h1:aSYyurHxEZSDy7kxhvZ4fH0inNkEEmRssZNbAmETR2c=
 | 
			
		||||
github.com/sendgrid/sendgrid-go v3.10.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
 | 
			
		||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 | 
			
		||||
github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086 h1:RYiqpb2ii2Z6J4x0wxK46kvPBbFuZcdhS+CIztmYgZs=
 | 
			
		||||
github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086/go.mod h1:PLPIyL7ikehBD1OAjmKKiOEhbvWyHGaNDjquXMcYABo=
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
package handler
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
| 
						 | 
				
			
			@ -13,6 +14,7 @@ import (
 | 
			
		|||
	"github.com/labstack/echo-contrib/session"
 | 
			
		||||
	"github.com/labstack/echo/v4"
 | 
			
		||||
	"github.com/labstack/gommon/log"
 | 
			
		||||
	"github.com/ngoduykhanh/wireguard-ui/emailer"
 | 
			
		||||
	"github.com/ngoduykhanh/wireguard-ui/model"
 | 
			
		||||
	"github.com/ngoduykhanh/wireguard-ui/util"
 | 
			
		||||
	"github.com/rs/xid"
 | 
			
		||||
| 
						 | 
				
			
			@ -194,6 +196,52 @@ func NewClient() echo.HandlerFunc {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EmailClient handler to sent the configuration via email
 | 
			
		||||
func EmailClient(mailer emailer.Emailer) echo.HandlerFunc {
 | 
			
		||||
	type clientIdEmailPayload struct {
 | 
			
		||||
		ID    string `json:"id"`
 | 
			
		||||
		Email string `json:"email"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return func(c echo.Context) error {
 | 
			
		||||
		// access validation
 | 
			
		||||
		validSession(c)
 | 
			
		||||
		var payload clientIdEmailPayload
 | 
			
		||||
		c.Bind(&payload)
 | 
			
		||||
		// TODO validate email
 | 
			
		||||
 | 
			
		||||
		clientData, err := util.GetClientByID(payload.ID, true)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Errorf("Cannot generate client id %s config file for downloading: %v", payload.ID, err)
 | 
			
		||||
			return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Client not found"})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// build config
 | 
			
		||||
		server, _ := util.GetServer()
 | 
			
		||||
		globalSettings, _ := util.GetGlobalSettings()
 | 
			
		||||
		config := util.BuildClientConfig(*clientData.Client, server, globalSettings)
 | 
			
		||||
 | 
			
		||||
		cfg_att := emailer.Attachment{"wg0.conf", []byte(config)}
 | 
			
		||||
		qrdata, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(clientData.QRCode, "data:image/png;base64,"))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "decoding: " + err.Error()})
 | 
			
		||||
		}
 | 
			
		||||
		qr_att := emailer.Attachment{"wg.png", qrdata}
 | 
			
		||||
		err = mailer.Send(
 | 
			
		||||
			clientData.Client.Name,
 | 
			
		||||
			payload.Email,
 | 
			
		||||
			"Your Wireguard configuration",
 | 
			
		||||
			"instructions here",
 | 
			
		||||
			[]emailer.Attachment{cfg_att, qr_att},
 | 
			
		||||
		)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Email sent successfully"})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateClient handler to update client information
 | 
			
		||||
func UpdateClient() echo.HandlerFunc {
 | 
			
		||||
	return func(c echo.Context) error {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										9
									
								
								main.go
								
								
								
								
							
							
						
						
									
										9
									
								
								main.go
								
								
								
								
							| 
						 | 
				
			
			@ -4,10 +4,13 @@ import (
 | 
			
		|||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	rice "github.com/GeertJohan/go.rice"
 | 
			
		||||
	"github.com/labstack/echo/v4"
 | 
			
		||||
 | 
			
		||||
	"github.com/ngoduykhanh/wireguard-ui/emailer"
 | 
			
		||||
	"github.com/ngoduykhanh/wireguard-ui/handler"
 | 
			
		||||
	"github.com/ngoduykhanh/wireguard-ui/router"
 | 
			
		||||
	"github.com/ngoduykhanh/wireguard-ui/util"
 | 
			
		||||
| 
						 | 
				
			
			@ -30,6 +33,9 @@ func init() {
 | 
			
		|||
	// update runtime config
 | 
			
		||||
	util.DisableLogin = *flagDisableLogin
 | 
			
		||||
	util.BindAddress = *flagBindAddress
 | 
			
		||||
	util.SendgridApiKey = os.Getenv("SENDGRID_API_KEY")
 | 
			
		||||
	util.EmailFrom = os.Getenv("EMAIL_FROM")
 | 
			
		||||
	util.EmailFromName = os.Getenv("EMAIL_FROM_NAME")
 | 
			
		||||
 | 
			
		||||
	// print app information
 | 
			
		||||
	fmt.Println("Wireguard UI")
 | 
			
		||||
| 
						 | 
				
			
			@ -69,9 +75,12 @@ func main() {
 | 
			
		|||
		app.POST("/login", handler.Login())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sendmail := emailer.NewSendgridApiMail(util.SendgridApiKey, util.EmailFromName, util.EmailFrom)
 | 
			
		||||
 | 
			
		||||
	app.GET("/logout", handler.Logout())
 | 
			
		||||
	app.POST("/new-client", handler.NewClient())
 | 
			
		||||
	app.POST("/update-client", handler.UpdateClient())
 | 
			
		||||
	app.POST("/email-client", handler.EmailClient(sendmail))
 | 
			
		||||
	app.POST("/client/set-status", handler.SetClientStatus())
 | 
			
		||||
	app.POST("/remove-client", handler.RemoveClient())
 | 
			
		||||
	app.GET("/download", handler.DownloadClient())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,6 +30,34 @@ Wireguard Clients
 | 
			
		|||
    </div>
 | 
			
		||||
</section>
 | 
			
		||||
 | 
			
		||||
<div class="modal fade" id="modal_email_client">
 | 
			
		||||
    <div class="modal-dialog">
 | 
			
		||||
        <div class="modal-content">
 | 
			
		||||
            <div class="modal-header">
 | 
			
		||||
                <h4 class="modal-title">Email Configuration</h4>
 | 
			
		||||
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
 | 
			
		||||
                    <span aria-hidden="true">×</span>
 | 
			
		||||
                </button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <form name="frm_email_client" id="frm_email_client">
 | 
			
		||||
                <div class="modal-body">
 | 
			
		||||
                    <input type="hidden" id="_client_id" name="_client_id">
 | 
			
		||||
                    <div class="form-group">
 | 
			
		||||
                        <label for="_client_email" class="control-label">Email</label>
 | 
			
		||||
                        <input type="text" class="form-control" id="_client_email" name="client_email">
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="modal-footer justify-content-between">
 | 
			
		||||
                    <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
 | 
			
		||||
                    <button type="submit" class="btn btn-success">Send</button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </form>
 | 
			
		||||
        </div>
 | 
			
		||||
        <!-- /.modal-content -->
 | 
			
		||||
    </div>
 | 
			
		||||
    <!-- /.modal-dialog -->
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div class="modal fade" id="modal_edit_client">
 | 
			
		||||
    <div class="modal-dialog">
 | 
			
		||||
        <div class="modal-content">
 | 
			
		||||
| 
						 | 
				
			
			@ -243,6 +271,8 @@ Wireguard Clients
 | 
			
		|||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // Edit client modal event
 | 
			
		||||
        $(document).ready(function () {
 | 
			
		||||
            $("#modal_edit_client").on('shown.bs.modal', function (event) {
 | 
			
		||||
| 
						 | 
				
			
			@ -308,6 +338,31 @@ Wireguard Clients
 | 
			
		|||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // submitEmailClient function for sending an email to the client with the configuration
 | 
			
		||||
        function submitEmailClient() {
 | 
			
		||||
            const client_id = $("#_client_id").val();
 | 
			
		||||
            const email = $("#_client_email").val();
 | 
			
		||||
            const data = {"id": client_id, "email": email};
 | 
			
		||||
            $.ajax({
 | 
			
		||||
                cache: false,
 | 
			
		||||
                method: 'POST',
 | 
			
		||||
                url: '/email-client',
 | 
			
		||||
                dataType: 'json',
 | 
			
		||||
                contentType: "application/json",
 | 
			
		||||
                data: JSON.stringify(data),
 | 
			
		||||
                success: function(resp) {
 | 
			
		||||
                    $("#modal_email_client").modal('hide');
 | 
			
		||||
                    toastr.success('Sent email to client successfully');
 | 
			
		||||
                     // Refresh the home page (clients page) after sending email successfully
 | 
			
		||||
                    location.reload();
 | 
			
		||||
                },
 | 
			
		||||
                error: function(jqXHR, exception) {
 | 
			
		||||
                    const responseJson = jQuery.parseJSON(jqXHR.responseText);
 | 
			
		||||
                    toastr.error(responseJson['message']);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // submitEditClient function for updating an existing client
 | 
			
		||||
        function submitEditClient() {
 | 
			
		||||
            const client_id = $("#_client_id").val();
 | 
			
		||||
| 
						 | 
				
			
			@ -350,13 +405,48 @@ Wireguard Clients
 | 
			
		|||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Edit client form validation
 | 
			
		||||
        $(document).ready(function () {
 | 
			
		||||
            $.validator.setDefaults({
 | 
			
		||||
                submitHandler: function () {
 | 
			
		||||
        // submitHandler
 | 
			
		||||
        function submitHandler(form) {
 | 
			
		||||
            const formId = $(form).attr('id');
 | 
			
		||||
            if (formId === "frm_edit_client") {
 | 
			
		||||
                submitEditClient();
 | 
			
		||||
            }else if (formId === "frm_email_client") {
 | 
			
		||||
                submitEmailClient();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $("#modal_email_client").on('shown.bs.modal', function (event) {
 | 
			
		||||
            let modal = $(this);
 | 
			
		||||
            const button = $(event.relatedTarget);
 | 
			
		||||
            const client_id = button.data('clientid');
 | 
			
		||||
            $.ajax({
 | 
			
		||||
                cache: false,
 | 
			
		||||
                method: 'GET',
 | 
			
		||||
                url: '/api/client/' + client_id,
 | 
			
		||||
                dataType: 'json',
 | 
			
		||||
                contentType: "application/json",
 | 
			
		||||
                success: function (resp) {
 | 
			
		||||
                    const client = resp.Client;
 | 
			
		||||
 | 
			
		||||
                    modal.find(".modal-title").text("Email Client " + client.name);
 | 
			
		||||
                    modal.find("#_client_id").val(client.id);
 | 
			
		||||
                    modal.find("#_client_email").val(client.email);
 | 
			
		||||
                },
 | 
			
		||||
                error: function (jqXHR, exception) {
 | 
			
		||||
                    const responseJson = jQuery.parseJSON(jqXHR.responseText);
 | 
			
		||||
                    toastr.error(responseJson['message']);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        $(document).ready(function () {
 | 
			
		||||
            $.validator.setDefaults({
 | 
			
		||||
                submitHandler: function (form) {
 | 
			
		||||
                    //submitEditClient();
 | 
			
		||||
                    submitHandler(form);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            // Edit client form validation
 | 
			
		||||
                $("#frm_edit_client").validate({
 | 
			
		||||
                    rules: {
 | 
			
		||||
                        client_name: {
 | 
			
		||||
| 
						 | 
				
			
			@ -388,6 +478,33 @@ Wireguard Clients
 | 
			
		|||
                        $(element).removeClass('is-invalid');
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // Email client form validation
 | 
			
		||||
                $("#frm_email_client").validate({
 | 
			
		||||
                rules: {
 | 
			
		||||
                    client_email: {
 | 
			
		||||
                        required: true,
 | 
			
		||||
                        email: true,
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
                messages: {
 | 
			
		||||
                    client_email: {
 | 
			
		||||
                        required: "Please enter an email address",
 | 
			
		||||
                        email: "Please enter a valid email address"
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
                errorElement: 'span',
 | 
			
		||||
                errorPlacement: function (error, element) {
 | 
			
		||||
                    error.addClass('invalid-feedback');
 | 
			
		||||
                    element.closest('.form-group').append(error);
 | 
			
		||||
                },
 | 
			
		||||
                highlight: function (element, errorClass, validClass) {
 | 
			
		||||
                    $(element).addClass('is-invalid');
 | 
			
		||||
                },
 | 
			
		||||
                unhighlight: function (element, errorClass, validClass) {
 | 
			
		||||
                    $(element).removeClass('is-invalid');
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    </script>
 | 
			
		||||
{{end}}
 | 
			
		||||
| 
						 | 
				
			
			@ -4,4 +4,7 @@ package util
 | 
			
		|||
var (
 | 
			
		||||
	DisableLogin   bool
 | 
			
		||||
	BindAddress    string
 | 
			
		||||
	SendgridApiKey string
 | 
			
		||||
	EmailFrom      string
 | 
			
		||||
	EmailFromName  string
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue