Coder Social home page Coder Social logo

sendgrid-rs's Introduction

sendgrid-rs

Unofficial Rust library for the SendGrid API.

This crate requires Rust 1.15 or higher as it uses a crate that has a custom derive implementation.

sendgrid-rs implements all of the functionality of other supported SendGrid client libraries. To use sendgrid-rs you must first create a SendGrid account and generate an API key. To create an API key for your SendGrid account, use the account management interface or see the SendGrid API Documentation.

sendgrid-rs is available on crates.io and can be included in your Cargo.toml as follows:

[dependencies]
sendgrid = "X.X.X"

Build Dependencies

This library utilises reqwest. Follow the instructions on the reqwest README in order to enable sending HTTPS requests to the SendGrid API.

Features

You can take advantage of a couple features for the crate. To enable the blocking send function, you can use the blocking flag. To enable the rustls TLS feature, use the rustls flag.

Example

An example of using this library can be found in the examples directory. This example code expects to find your SendGrid API key in the process environment. In shells such as Bash or ZSH this can be set as follows:

export SENDGRID_API_KEY="SG.my.api.key"
SENDGRID_API_KEY=SG.... cargo run --example v3_disable_tracking --features="blocking" [email protected] [email protected]

Documentation

Documentation

Please don't hesitate to contact me at the email listed in my profile. I will try to help as quickly as I can. If you would like to contribute, contact me as well.

Mentions

Thanks to meehow for their contributions.

Thanks to richo for their improvements to the V2 API.

License

MIT

sendgrid-rs's People

Contributors

b00kdev avatar brianjob avatar brizental avatar coolreader18 avatar cyphersnake avatar dcampbell24 avatar ecclarke42 avatar elrendio avatar enfipy avatar gopherj avatar gsquire avatar hgzimmerman avatar ia0 avatar incker2 avatar konradjniemiec avatar lholden avatar luisholanda avatar meehow avatar nicholas-l avatar omid avatar otavio avatar pastoraleman avatar richo avatar ruseinov avatar sinking-point avatar szabgab avatar tarang avatar thoucheese avatar vegetarianorc avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

sendgrid-rs's Issues

The library would benefit from simpler constructors and shortcuts

Version 0.8 is so much better to use than before ! (#43)
However, not allowing to use Email::new() without passing an email would make more sense in my opinion, since the "Email" concept doesn't really exist if it is not set (as is very well held by the Email struct typing), so we shouldn't allow building an Email struct and forgetting to set the email field of that struct.
As a consequence, I would replace the :

sg::Email::new()
	.set_email("[email protected]")
	.set_name("Some Person")

by :

sg::Email::new("[email protected]") // Possibly still allow set_name but chances are it wouldn't be needed by any clean code
// and
sg::Email::with_name("[email protected]", "Some Person")

and remove the Default derive on that type.

In a similar fashion :

sg::Personalization::with_to(email)
// as an additional constructor for
sg::Personalization::new().add_to(email)

and

sg::Message::new().add_to(email)
// as a helper for
sg::Message::new().add_personalization(sg::Personalization::new().add_to(email))

would make the library even less verbose and more practical to use.

Rust 2018 update causing V3Sender to result in 415 response

When updating a project to Rust 2018, a previously working sendgrid email setup returns a 415 StatusCode (Unsupported Media Type).

When using sendgrid-rs 0.7.2 the the V3Sender content-type header looks like...

headers.insert(
    header::CONTENT_TYPE,
    HeaderValue::from_static("application/x-www-form-urlencoded"),
);

However, the master branch has changed the content type to application/json which causes the emails to send correctly.

It looks like the master branch can be deployed as it is?

Authorization failed

I made the same example using sendgrid-rs and sendgrid-nodejs

const sgMail = require("@sendgrid/mail");
sgMail.setApiKey(
    "xxxx"
);
const msg = {
    to: "[email protected]",
    from: "[email protected]", // Use the email address or domain you verified above
    subject: "Sending with Twilio SendGrid is Fun",
    text: "and easy to do anywhere, even with Node.js",
    html: "<strong>and easy to do anywhere, even with Node.js</strong>",
};
//ES6
sgMail.send(msg).then(
    () => {},
    error => {
        console.error(error);

        if (error.response) {
            console.error(error.response.body);
        }
    }
)
//ES8
;(async () => {
    try {
        await sgMail.send(msg);
    } catch (error) {
        console.error(error);

        if (error.response) {
            console.error(error.response.body);
        }
    }
})();
use std::collections::HashMap;

use sendgrid::errors::SendgridError;
use sendgrid::v3::*;

#[tokio::main]
async fn main() -> Result<(), ()> {
    println!("Hello, world!");
    let mut cool_header = HashMap::new();
    cool_header.insert(String::from("x-cool"), String::from("indeed"));
    cool_header.insert(String::from("x-cooler"), String::from("cold"));

    let p = Personalization::new()
        .add_to(Email::new().set_email("[email protected]"))
        .add_headers(cool_header);

    let m = Message::new()
        .set_from(Email::new().set_email("[email protected]"))
        .set_subject("Subject")
        .add_content(
            Content::new()
                .set_content_type("text/html")
                .set_value("Test"),
        )
        .add_personalization(p);

    // let mut env_vars = ::std::env::vars();
    // let api_key = env_vars.find(|v| v.0 == "SG_API_KEY").unwrap();
    let sender = Sender::new(
"xxxx"
    );
    let resp = sender.send(&m).await.unwrap();
    println!("status: {}", resp.status());
    Ok(())
}

Update to `reqwest` 0.11

Hi!

I'm using this library for a project at work and just upgraded our server to use the tokio 1.0 runtime. Sending emails has broken with that change, showing a "not currently running on the Tokio runtime" error, so it seems like reqwest's tokio version needs to match whatever is running.

Luckily, this should not require any code changes (just bumping reqwest and tokio versions), but might require a version change/new release on crates.io.

Return a custom sendgrid error

There are a lot of projects that do this. I think it makes sense for this library to return custom send grid errors instead of hyper's stuff.

"rustls" feature now depends on "native-tls"

0.11.3 did not have a dependency on "openssl":

[dependencies]
sendgrid = { version = "0.11.3", features = ["rustls"] }
cargo tree -i -p native-tls
error: package ID specification `native-tls` matched no packages

Now with 0.13.0:

[dependencies]
sendgrid = { version = "0.13.0", features = ["rustls"] }

There is a dependency upon "native-tls":

cargo tree -i -p native-tls
native-tls v0.2.4
├── hyper-tls v0.4.3
│   └── reqwest v0.10.8
│       └── sendgrid v0.13.0
│           └── sendgrid-rs-issue v0.1.0 (/tmp/rust-sendgrid)
├── reqwest v0.10.8 (*)
└── tokio-tls v0.3.1
    ├── hyper-tls v0.4.3 (*)
    └── reqwest v0.10.8 (*)

It is no longer possible to cross-compile sendgrid-rs to x86_64-unknown-linux-musl.

Docs

Move docs to GitHub pages for easier management.

Async feature violates additive property

Cargo features are required to be additive to function properly when a crate is used transitively multiple times in a complete program.

The async feature violates this property by changing the type of SGClient::send in an incompatible way. Would it be possible to convert the async implementation to a separate method with its own name (or maybe make a different async client type)?

Test Feature Gates

We should configure TravisCI to test the crate using the various feature flags.

dynamic_template_data uses SGMap instead of a more general JSON friendly type

The official v3 documentation lists the definition of dynamic_template_data as

This field is a collection of key/value pairs following the pattern "variable_name":"value to insert".

This is a bit mischaracterized, since technically any json object of any nesting level is supported: official example

SGMap fits the spec definition exactly, but is very limiting, because json serializable objects with any nesting can't be easily transformed into this type. You would need to serialize every member object to a string but only at a depth of 1. In order to do programatically, a complicated declarative macro would have to be written that iterates over each top level field that is serde serializable.

However all official client libraries use some variation of Map<String, Object> to define dynamic_template_data:

  1. Go
  2. Java
  3. Python

I propose to:

  1. change dynamic_template_data to Option<serde_json::value::Value>, and modify add_dynamic_template_data to use serde_json::to_value. HashMap<String, String> is serde::Serialize and should not panic, so we can safely unwrap that to_value call, not needing to change the signature.
  2. add a new method to Personalization, add_dynamic_template_data_json that instead of a SGMap takes a &T where <T: serde::Serialize+?Sized>, and again calls serde_json::to_value on this and stores the resulting owned serde_json::value::Value. Since this feasibly could error with improper input, we may want to return a Result or Option<Error> here, happy to iterate on the proper error propagation.

reqwest does something similar for their json body implementation.

Since dynamic_template_data is not pub, and add_dynamic_template_data signature doesn't change, this will not be a breaking change.

One alternative here is to avoid storing dynamic_template_data as a serde_json::value::Value since it is a concrete type from another library, and instead use a Box<dyn serde::Serialize + ?Sized>, however this is arguably less clean and will store the object on the heap.

Another edge case to consider is we would no longer be enforcing the format of a json object, theoretically someone could pass a random string that is not in the format of key:value into add_dynamic_template_data_json, however we would expect sendgrid to handle this for us. I could even do some testing here to see if its a major issue or not.

Let me know if this makes sense and I'll start on a PR!

Use reqwest

Since the reqwest crate has emerged, we should switch over to it to handle the SSL and header boilerplate that exists now.

Return message id from v3 send API

I need to access the message id returned by the v3 send API, but couldn't see a way to do it. Am I missing something?

To get something working in the meantime, I ended up making a branch that returns it instead of the response status, getting the value out of the X-Message-Id header. Is there some version of that change that would be worth me tidying up and opening as a PR?

V3 structures should be easier to construct

Right now building a simple mail is so much boilerplate.

  • They should provide with_xx constructors
  • They should allow for let x = X::new().add_something().add_something_else() (add_something(mut self) -> Self)

when SG returns error, library happily returns success

Hey @gsquire, I've tried to send message and it returned Ok with the following error:

{"errors":[{"message":"If present, text/plain must be first, followed by text/html, followed by any other content.","field":"content","help":null}]}

I wonder if SG returns successful status code in this case?

How to disable tracking?

I am using the following code to send messages via sendgrid. I was trying to open and click tracking. I found TrackingSettings, but so far I have not understood how to use it. It would be nice if you could add an example doing so.

use sendgrid::SGClient;
use sendgrid::{Destination, Mail};

use crate::EmailAddress;

pub async fn sendgrid(
    api_key: &str,
    from: &EmailAddress,
    to: &EmailAddress,
    subject: &str,
    html: &str,
) {
    let sg = SGClient::new(api_key);

    let mail_info = Mail::new()
        .add_to(Destination {
            address: &to.email,
            name: &to.name,
        })
        .add_from(&from.email)
        .add_from_name(&from.name)
        .add_subject(subject)
        .add_html(html);

    sg.send(mail_info).await.ok();
}

`RequestNotSuccessful` lacks error details

I'm getting this error:

#[fail(display = "Request failed with StatusCode: {}", _0)]
RequestNotSuccessful(reqwest::StatusCode, String),

But the way display is implemented does not allow me to get the error message, and as a consequence it's hard to figure out why my emails don't send (I have to update my code to turn it into an error that actually shows the body).

I think it would be great if some form of additional information could be extracted from the body and used in the Display impl, so it would be easier to figure out why the emails don't send.

Unclear branching structure

My understanding is that master is intended to become the 0.8 release however the 0.7.x branch and release tags are not pushed.

Please take a look at this since it is hard to know where to look for checking for fixes and changes.

Can't set reply_to in the V3 API

The V3 API has a reply_to field in the personalisations. This is missing from sendgrid::v3::Personalization, and it's impossible to set it via the headers object.

An add_reply_to method needs to be added to Personalizations. This would be analogous to add_to etc.

Type system is not leveraged to avoid mistakes when building sendgrid requests

The library provides lots of way to build Sendgrid queries that have zero chance to succeed:
Examples:

sender.send(&Message::new())?;
sender.send(&Message::new()
			.set_from(self.from.clone())
			.set_subject(subject)
			.add_personalization(Personalization::new().add_to(Email::new().set_email(to_email)))
			.add_content(Content::new().set_value(body)))?;
sender.send(&Message::new()
			.set_subject(subject)
			.add_personalization(Personalization::new().add_to(Email::new().set_email(to_email)))
			.add_content(Content::new().set_content_type("text/html").set_value(body)))?;
sender.send(&Message::new()
			.set_from(self.from.clone())
			.add_personalization(Personalization::new().add_to(Email::new().set_email(to_email)))
			.add_content(Content::new().set_content_type("text/html").set_value(body)))?;
sender.send(&Message::new()
			.set_from(self.from.clone())
			.set_subject(subject)
			.add_content(Content::new().set_content_type("text/html").set_value(body)))?;
sender.send(&Message::new()
			.set_from(self.from.clone())
			.set_subject(subject)
			.add_personalization(Personalization::new().add_to(Email::new().set_email(to_email)))
			.add_content(Content::new().set_content_type("text/htlm").set_value(body)))?;

It's already been several times we've gotten trapped by this, resulting in broken deployed software.

Given the amount of fields that are mandatory/have restricted values, there should only be, as much as possible, constructors that respect the Sendgrid constraints.

e.g.:

sender.send(Message::new(
   "[email protected]",
   "[email protected]",
   Content::html("subject", "<strong>body</strong>"),
)

where Content could be:

#[serde(untagged)] // and flattened into the message
pub enum Content<'a> {
    SubjectAndBody {
        subject: &'a str,
        body: Body<'a>,
    }
    Template {
        // If I understand correctly https://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/index.html
        // specifying a template id makes subject and body optional
        template_id: &'a str,
        subject: Option<&'a str>,
        body: Option<Body<'a>>,
    }
}

pub struct Body<'a> {
    content_type: ContentType,
    body: &'a str,
}

pub enum ContentType {
    Html,
    PlainText,
}

etc.

I feel like providing typing for an API is be the whole point of this kind of library. Otherwise if I'm going to have to read the Sendgrid spec anyway to figure which fields are mandatory and where I should put them, I almost might as well write my own serde SDK.

Update to stable JSON

Now that Rust stable supports custom derive, migrate away from the old build script that is currently in use.

form_encoder

@gsquire

What do you think about using this library for the building of the form encoded data during creation of the message body?

[dependencies]
url = "1.0.0"

extern crate url;
use url::form_urlencoded;

fn main() {
    let encoded: String = form_urlencoded::Serializer::new(String::new())
          .append_pair("to[]", "[email protected]")
          .append_pair("saison", "Été+hiver")
          .finish();
    println!("{:?}", encoded);
}

Output: to%5B%5D=bob%40example.com&saison=%C3%89t%C3%A9%2Bhiver

Make Sender own a reqwest::Client

Per Reqwest's documentation:

The Client holds a connection pool internally, so it is advised that you create one and reuse it.

I anticipate that keeping connections alive by not dropping the Client should improve throughput if library users are sending many unique emails at once using the same Sender.

This should be doable by using #[cfg(feature = "async")] and #[cfg(not(feature = "async"))] to control which Client type is a member of Sender. I don't anticipate that such a change would affect any other properties of the library beyond decreasing the time to send subsequent messages using the same Sender.

I'm happy to implement this myself if you indicate that you would merge such a change.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.