Giter VIP home page Giter VIP logo

sparkpost_rails's Introduction

Gem Version Build Status

SparkPost Rails

This gem provides seamless integration of SparkPost with ActionMailer. It provides a delivery_method based upon the SparkPost API, and makes getting setup and sending email via SparkPost in a Rails app pretty painless.

Getting Started

Add the gem to your Gemfile

gem 'sparkpost_rails'

Then run the bundle command to install it.

By default, the gem will look for your SparkPost API key in your environment, with the key SPARKPOST_API_KEY. You can override this setting by identifying a different key in the initializer (config/initializers/sparkpost_rails.rb):

SparkPostRails.configure do |c|
  c.api_key = 'YOUR API KEY'
end

Note that an initializer file is not required to use this gem. If an initializer is not provided, default values will be used. See "Additional Configuration" below for a list of all the default settings.

In each environment configuration file from which you want to send emails via Sparkpost, (i.e. config/environments/production.rb) add

config.action_mailer.delivery_method = :sparkpost

Additional Configuration

You can establish values for a number of SparkPost settings in the initializer. These values will be used for every message sent from your application. You can override these settings on individual messages.

SparkPostRails.configure do |c|
  c.api_endpoint = "https://api.eu.sparkpost.com/api/" # default: "https://api.sparkpost.com/api/"
  c.sandbox = true                                     # default: false
  c.track_opens = true                                 # default: false
  c.track_clicks = true                                # default: false
  c.return_path = '[email protected]'       # default: nil
  c.campaign_id = 'YOUR-CAMPAIGN'                      # default: nil
  c.transactional = true                               # default: false
  c.ip_pool = "MY-POOL"                                # default: nil
  c.inline_css = true                                  # default: false
  c.html_content_only = true                           # default: false
  c.subaccount = "123"                                 # default: nil
end

Usage

When calling the deliver! method on the mail object returned from your mailer, SparkPostRails provides the response data directly back from SparkPost as a hash.

result = MyMailer.welcome_message(user).deliver!

Example:

{
  "total_rejected_recipients" => 0,
  "total_accepted_recipients" => 1,
  "id" => "00000000000000"
}

If the SparkPost API responds with an error condition, SparkPostRails will raise a SparkPostRails::DeliveryException, which will include all the message data returned by the API.

SparkPostRails will support multiple recipients, multiple CC, multiple BCC, ReplyTo address, file attachments, inline images, multi-part (HTML and plaintext) messages - all utilizing the standard ActionMailer methodologies.

Handling Errors

If you are using ActiveJob and wish to do something special when the SparkPost API responds with an error condition you can do so by rescuing these exceptions via ActionMailer::DeliveryJob. Simply add an initializer:

config/initializers/action_mailer.rb

ActionMailer::DeliveryJob.rescue_from(SparkPostRails::DeliveryException) do |exception|
  # do something special with the error
  # do something special with the error
end

SparkPost-Specific Features

Configuration Settings

You can specify values for any or all of the configuration settings listed above on an individual message. Simply add a hash of these values to the mail message in a field named sparkpost_data:

data = {
  track_opens: true,
  track_clicks: false,
  campaign_id: 'My Campaign',
  transactional: true,
  ip_pool: 'SPECIAL_POOL',
  api_key: 'MESSAGE_SPECIFIC_API_KEY',
  subaccount: '123'
}

mail(to: to_email, subject: "Test", body: "test", sparkpost_data: data)

Additionally, return_path can be overridden on a specific email by setting that field on the mail message itself:

mail(to: to_email, subject: "Test", body: "test", return_path: "[email protected]")

Transmission Specific Settings

For an individual transmisison you can specifiy that SparkPost should ignore customer supression rules - if your SparkPost account allows for this feature. Simply include the flag in the sparkpost_data field on the message:

data = { skip_suppression: true }

mail(to: to_email, subject: "Test", body: "test", sparkpost_data: data)

To schedule the generation of messages for a future date and time, specify a start time in the date parameter of the mail. The date must be in the future and less than 1 year from today. If date is in the past or too far in the future, no date will be passed, and no delivery schedule will be set.

start_time = DateTime.now + 4.hours

mail(to: to_email, subject: "Test", body: "test", date: start_time)

You can set a description for a transmission via the sparkpost_data as well. The maximum length of the decription is 1024 characters - values longer than the maxium will be truncated.

data = { description: "My Important Message" }

mail(to: to_email, subject: "Test", body: "test", sparkpost_data: data)

By default, content from single-part messages is sent at plain-text. If you are only intending to send HTML email, with no plain-text part, you can specify this as shown below. You can also set this in the configuration to ensure that all single-part emails are sent as HTML.

data = { html_content_only: true }

mail(to: to_email, subject: "Test", body: "<h1>test</h1>", sparkpost_data: data)

Subaccounts

SparkPostRails supports sending messages via subaccounts in two ways. The default API key set in the configuration can be overriden on a message-by-message basis with a subaccount API key.

data = { api_key: "SUBACCOUNT_API_KEY" }

mail(subject: "Test", body: "test", sparkpost_data: data)

Subaccounts can also be leveraged using the subaccount ID with the master API key.

data = { subaccount: "123" }

mail(subject: "Test", body: "test", sparkpost_data: data)

Recipient Lists

SparkPostRails supports using SparkPost stored recipient lists. Simply add the list_id to the sparkpost_data hash on the mail message:

data = { recipient_list_id: "MY-LIST"}

mail(subject: "Test", body: "test", sparkpost_data: data)

NOTE: If you supply a recipient list_id, all To:, CC:, and BCC: data specified on the mail message will be ignored. The SparkPost API does not support utilizing both a recipient list and inline recipients.

Substitution Data

You can leverage SparkPost's substitution engine through the gem as well. To supply substitution data, simply add your hash of substitution data to your sparkpost_data hash, with the key substitution_data.

sub_data = {
  first_name: "Sam",
  last_name: "Test
}

data = { substitution_data: sub_data }

mail(to: to_email, subject: "Test", body: "test", sparkpost_data: data)

Recipient-Specific Data

When sending to multiple recipients, you can pass an array of data to complement each recipient. Simply pass an array called recipients containing an array of the additional data (e.g. substitution_data).

recipients = ['[email protected]', '[email protected]']
sparkpost_data = {
  recipients: [
    { substitution_data: { name: 'Recipient1' } },
    { substitution_data: { name: 'Recipient2' } }
  ]
}
mail(to: recipients, sparkpost_data: sparkpost_data)

Using SparkPost Templates

You can leverage SparkPost's powerful templates rather than building ActionMailer views using SparkPostRails. Add your template_id to the sparkpost_data hash. By default, ActionMailer finds a template to use within views. A workaround to prevent this default action is to explicitly pass a block with an empty text part:

data = { template_id: "MY-TEMPLATE" }

mail(to: to_email, sparkpost_data: data) do |format|
  format.text { render text: "" }
end

NOTE: All inline-content that may exist in your mail message will be ignored, as the SparkPost API does not accept that data when a template id is supplied. This includes Subject, From, ReplyTo, Attachments, and Inline Images.

###Other Mail Headers If you need to identify custom mail headers for your messages, use the ActionMailer header[] method. The gem will pass all appropriate headers through to the API. Note, per the SparkPost API documentation

Headers such as 'Content-Type' and 'Content-Transfer-Encoding' are not allowed here as they are auto-generated upon construction of the email.

headers["Priority"] = "urgent"
headers["Sensitivity"] = "private"

mail(to: to_email, subject: "Test", body: "test")

sparkpost_rails's People

Contributors

alvingarcia avatar bosunolanrewaju avatar bradherman avatar bradybonnette avatar davekiss avatar debanne avatar dgoerlich avatar giedriusksd avatar hampei avatar kevinkimball avatar ksykulev avatar norbertszivos avatar patricklef avatar shekibobo avatar sunny avatar throttleup avatar viamin avatar yarikov 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sparkpost_rails's Issues

Can't send email to Multiple recipients

I'm sending multiple email addresses but the problem i'm running into is that everyone in the "TO" list sees their email address there and others who are known to be supposed to be "TO" field are not getting the email because they don't see them there.

So there is way to display all "To" recipients ?

Turn off Sparkpost SMTP dynamically

Hi,

Is it possible to turn off this gem on the fly? In my case, I want people to use their own SMTP settings but when I try emails are still sent through SparkPost.

Thanks

`sparkpost_data`is not persisted when mail is deserialized

We are using sparkpost_rails with DelayedJob. DelayedJob serializes the mail object to a database table using YAML, for later execution. When the job is deserialized from YAML, its enclosed Mail::Message instance is also deserialized, however the sparkpost_data method is undefined. Since this method is defined inside of the mail method in DataOptions::InstanceMethods, any Mail::Message instance created outside of this method (e.g. through deserialization) will not have the method present. This means all of our emails fail to send, with (e.g.) #<NoMethodError: undefined method sparkpost_data' for #Mail::Message:0x00007f90cdcc22d8>`.

I'll fork the code and experiment with some possible fixes, but if there's an obvious/ preferred approach, please do let me know and I'll pursue it.

1.5.3 -> 1.5.5

Just wondering if there are any known breaking changes, as I didn't see 1.5.5 listed in the changeling. Thank you for your work maintaining this project!

Respect perform_deliveries option

If perform_deliveries is set to true on the message, by a mail interceptor for example, sparkpost_rails should respect this by not calling the sparkpost api to transmit.

Invalid JSON errors with Ruby 2.6.0

So I think this is actually a Ruby / Net::HTTP issue but I thought I'd post this here in case other people run into the same problem. Here's the issue I opened on the Ruby issue tracker ( https://bugs.ruby-lang.org/issues/15472 ):

It seems that invalid (JSON) data is being sent once the request's body passes a size threshold. I'm sending emails via the sparkpost.com API using the sparkpost_rails gem ( https://github.com/the-refinery/sparkpost_rails ). That gem uses Net::HTTP to make its API calls.

I upgraded my app from Ruby 2.5.3 to Ruby 2.6.0 on December 25th. Yesterday, on the 26th, my app attempted to send its nightly emails to users. I was flooded with these errors from the API:

{ "errors": [ { "message": "invalid data format\/type", "description": "Problems parsing request as json", "code": "1300" } ] }

After some debugging / trial & error I discovered that the length of the email messages seemed to be the problem. I haven't nailed down the exact maximum length that will work but I know that if the request body is 16,511 characters long the message will be sent. If it's 17,396 characters it will fail.

I've also tried replacing Net::HTTP with the rest_client gem. The full-sized messages get sent with no problems using RestClient.

So I can replace this (https://github.com/the-refinery/sparkpost_rails/blob/master/lib/sparkpost_rails/delivery_method.rb#L379):

    def post_to_api
      url = "https://api.sparkpost.com/api/v1/transmissions"

      uri = URI.parse(url)
      http = Net::HTTP.new(uri.host, uri.port)
      http.use_ssl = true

      request = Net::HTTP::Post.new(uri.path, @headers)
      request.body = JSON.generate(@data)
      http.request(request)
    end

with

    def post_to_api
      url = "https://api.sparkpost.com/api/v1/transmissions"

      RestClient.post(url, JSON.generate(@data), @headers)

    end

and my (full-length, much larger than 17,396 characters) messages get sent with no problem.

I also tried logging the actual HTTP requests using the httplog gem (https://github.com/trusche/httplog) in Ruby 2.5.3 and 2.6.0. Unfortunately that showed the request payloads were identical. But I suspect that gem's logging does some conversion that isn't actually happening in the real data being sent to the SparkPost API.

Working example?

It would be really useful for Ruby newbies like me to have a working Rails example if possible :)

I've set up a basic Rails project and used the example code in the Readme, but when I call deliver! on my SparkPost mailer I get an SMTP error:

Net::SMTPAuthenticationError: 535 5.7.8 Sorry.

Complete examples of the steps needed to configure SparkPost Rails would be most welcome!

SparkPostRails::DeliveryException ([{"message"=>"Unauthorized."}])

I'm getting this message when I try to send an email:

Completed 500 Internal Server Error in 1174ms (ActiveRecord: 40.8ms)
SparkPostRails::DeliveryException ([{"message"=>"Unauthorized."}])

but the main problem is when I try with same data after some time it resolves automatically. I don't know what to do with this any suggestions how can I solve this?

ActionMailer still looking for ActionView mailer templates

I am not sure what setup I have missed. But for some reasons running the code below (sending template_id with sparkpost_data) gives ActionView::MissingTemplate Error

data = {
  template_id: 'my-template',
  substitution_data: 
    first_name: user.first_name
  }
}
mail(to: '[email protected]', sparkpost_data: data)

I have
config.action_mailer.delivery_method = :sparkpost in the application environment configuration file
'SPARKPOST_API_KEY' set in environment variables and this in initializers/sparkposts_rails.rb

SparkPostRails.configure do |c|
  c.transactional = true
end

Sparkpost throws 500 when email is invalid, catching error doesn't work

I followed the instructions in the README, setting up action_mailer.rb file to catch SparkPostRails::DeliveryException but it doesn't seem to work.

The issue is that if you use sparkpost with devise, and a user tries to register with an email that doesn't have any MX records... (example: [email protected]) sparkpost throws a DeliveryException, and my app crashes. Am I doing something wrong? Here's my action_mailer.rb file

ActionMailer::DeliveryJob.rescue_from(SparkPostRails::DeliveryException) do |exception|
  redirect_to '/logout', alert: "Please use a valid email address."
  # do something special with the error
end

Email sent as html format

This is my config/environments/development.rb

config.action_mailer.delivery_method = :sparkpost
config.action_mailer.smtp_settings = {
    :address              => 'smtp.sparkpostmail.com',
    :port                    => 587,
    :domain               => 'gmail.com', #you can also use google.com
    :enable_starttls_auto => true,
    :encryption          => 'STARTTLS'
  }

I put my api_key in config/initializer/sparkpost_rails.rb

i getting my email but my it uses html format

issue_sparkpost

SparkPostRails::DeliveryException ([{"message"=>"invalid data format/type"

I'm getting this message when I try to send an email:

SparkPostRails::DeliveryException ([{"message"=>"invalid data format/type", "description"=>"content.headers json dictionary value has type 'json_array' when expecting type 'string'", "code"=>"1300"}]):

Mailer:
mail(to: @user.email, subject: "subject", sparkpost_data: {track_opens: true, track_clicks: true})

Controller:
result = CertificateMailer.email_certificate(current_user).deliver!

Potential Problem with CC: ActionMailer Parameter

Hi, I am using sparkpost_rails 1.4.0 with rails 4.2.8

When I try to include an Ruby Array of Emails for the "cc" parameter for Action Mailer, I receive this error from SparkPost during delivery:

{"message"=>"invalid data format/type", "description"=>"content.headers json dictionary value has type 'json_array' when expecting type 'string'", "code"=>"1300"}

However, when I use the same exact array for a "bcc" parameter, the mail successfully goes through and the BCC work as I would expect.

Could there be a problem in how these headers are constructed?

Thanks in advance.

Rails 5.2.0 Support

The new Rails 5.2 was released today. I believe the only change that would need to be done is on the gemspec restriction.

SparkPost initializer is required, not optional

Maybe this is intended, but I had set my ENV variable for the API key and was planning on just using the default config that ships with this gem, and there were errors that prevented this from working unless the initializer actually existed.

Here's the one that I saw, but there may be more:

WARN: NoMethodError: undefined method `sandbox' for nil:NilClass
WARN: sparkpost_rails-2bbfc0a590cf/lib/sparkpost_rails/delivery_method.rb:219:in `prepare_sandbox_mode_from'

Attaching files

I don't quite get how to attach files to an email, I checked the API and it says it should be under the content.attachments section as an array, yet doing this doesn't seem to work, this is how I'm sending my email:

  def buyer_tickets_email(order)
    @order = order
    data = {
      substitution_data: purchase_order_details(@order),
      content: {
        attachments: order.tickets.map do |ticket|
          {
            name: "#{ticket_file_name(ticket)}.pdf",
            type: "application/pdf",
            data: ticket.to_pdf
          }
        end
      },
      template_id: 'buyer-tickets-email'
    }
    mail to: @order.email, subject: I18n.t('tickets_mailer.subject'), sparkpost_data: data
  end

Am I missing something? The email arrives correctly yet without the attached pdfs

sparkpost_data ignored

Seems in the created mail, the sparkpost_data has no effect.

I am trying to set metadata

sparkpost_data = {
    "id"=> @message.id, #@message.id, #maybe we can specifuy the same id Sparkpost uses as our id, will make it easier to search transmissions in sparkpost for debuggin
    "content"=> {}, # no need as that data is sent in the mail method below - actually I could not get options in here to work like reply_to
    "options"=>{}, # see config/initializers/sparkpost_rails.rb:3,
    "metadata"=>{"message_id"=> @message.id}#@message.id} #this data will be returned by sparkpost webhooks
}
mail(to:@message.recipient, subject: @message.subject, from: from_addr,sparkpost_data: sparkpost_data).deliver!

it is not just metadata, it also failed for all content attrs, and I would guess all attrs in sparkpost_data altogether but have not tested them all.

Using rails-4.2.9
sparkpost_rails-1.5.0
actionmailer-4.2.9

How can I get a response from Sparkpost?

After I send an email using:

result = MyMailer.some_message(some_var).deliver!

how can I get this response?

{"total_rejected_recipients"=>0, "total_accepted_recipients"=>1, "id"=>"00000000000000"}

The result variable is an instance of Mail::Message, so I can not get these values from that.

I need an id as I am using webhooks to track delivery, opens/clicks and so on...

Rails 7.0.0 Support Timeline?

Rails 7.0.0 final was released yesterday, and will soon become the new standard. I'm hoping we can see a PR like #89 get merged soon. Is this something that can be expected before long?

ts heart hands

Devise emails sending as plaintext.

attempting to isolate the problem, I have disabled tracking

# config/environments/production.rb
Rails.application.configure do
  config.action_mailer.delivery_method = :sparkpost

# config/initializers/sparkpost_rails.rb
SparkPostRails.configure do |c|
  c.api_key       = ENV.fetch('SPARKPOST_API_KEY')
  c.sandbox       = false
  c.track_clicks  = false
  c.track_opens   = false
  c.transactional = false
end

Emails sent with our custom mailer are sending fine, however Devise generated emails: Forgot password, are sending as plaintext when they should be html.

I'm trying to figure out what devise is doing with type, still looking...

TLS depreciation

Hi there ! I received this email from Sparkpost

TLS v1.1 Deprecation Notice
Dear valued customer,

On September 1, 2020, SparkPost will be deprecating TLSv1.1 fallback across all of our systems. This is an older security standard for sending email and contains vulnerabilities that directly impact the integrity and security of your communications; vulnerabilities which cannot be fixed in these older implementations.

As long as connections are made using TLSv1.2, this change will result in zero impact to your ongoing use of our service; however if connections are made using TLSv1.1, you will observe a failure to connect to API and SMTP endpoints. To ensure that there is no impact to existing processes, it is best to verify that your clients support TLSv1.2. You can verify the version of TLS that your client is using by collecting local packet captures of network traffic and inspecting which version of TLS is being used to establish a secure connection to SparkPost (handshake).

If you are using TLSv1.1, then you should take the following steps:

If you are using proprietary code, have your in-house development team update the code to use TLSv1.2.
If you are using 3rd party software, verify that your client supports TLSv1.2, and upgrade the client.

Do you have any idea if sparkpost_rails is using TLS > v1.2 ? Sorry if my question is not relevant, I'm not sure do understand very nice this protocol thing...

Inline images aren't displaying inline

Hi, thanks so much for your work on sparkpost_rails!

I'm trying to include inline images like this:

message[:attachments].each do |ad|
  attachments.inline[ad.original_filename] = {
     mime_type: ad.content_type,
     content: File.read(ad.tempfile),
     cid: 'some-name'
  }
end

However, the message arrives with a randomly generated Content-Id rather than the specified cid: parameter, and a missing image icon appears where the <img> tag is in HTML, with the attachment at the bottom of the message.

In this case, the email is received from Sparkpost webhook, so the content-id is already set in the incoming email. I parse it out, and then use that value in the cid: parameter in attachments.inline[] = {}. If it were actually used, the image would display inline where specified by the <img> tag, but the cid parameter seems to be ignored.

Passing `date` doesn't work

Doing this:

mail(subject: subject, sparkpost_data: {transactional: false, date: time})

Gives us this error:

SyntaxError: (eval):1: syntax error, unexpected tCONSTANT, expecting =>
{:transactional=>false, :date=>Sat, 13 May 2017 12:10:15 UTC +00:00}
                                          ^
	from /Users/david/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/sparkpost_rails-1.4.0/lib/sparkpost_rails/delivery_method.rb:46:in `eval'
	from /Users/david/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/sparkpost_rails-1.4.0/lib/sparkpost_rails/delivery_method.rb:46:in `find_sparkpost_data_from'
	from /Users/david/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/sparkpost_rails-1.4.0/lib/sparkpost_rails/delivery_method.rb:14:in `deliver!'
	from /Users/david/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/mail-2.6.5/lib/mail/message.rb:253:in `deliver!'
	from /Users/david/.rbenv/versions/2.3.1/lib/ruby/2.3.0/delegate.rb:83:in `method_missing'

Looks like the time gets converted to a string which is then subsequently not parsable by eval:

[3] pry(#<SparkPostRails::DeliveryMethod>)> mail[:sparkpost_data]
=> #<Mail::Field 0x7f9d7e8891e0 @name="sparkpost-data" @value={:transactional=>false, :date=>Sat, 13 May 2017 12:10:37 UTC +00:00} @raw_value=nil @charset="UTF-8" @field_order_id=100 @field=#<Mail::OptionalField:0x007f9d6352a4b0 @errors=[], @charset="UTF-8", @name="sparkpost-data", @length=nil, @tree=nil, @element=nil, @value="{:transactional=>false, :date=>Sat, 13 May 2017 12:10:37 UTC +00:00}">>

Avoid raising exception upon failure

Hi,

I'd like to propose that we don't raise an exception in SparkPostRails::DeliveryMethod#process_result()

    def process_result result
      result_data = JSON.parse(result.body)

      if result_data["errors"]
        @response = result_data["errors"]
        raise SparkPostRails::DeliveryException, @response
      else
        @response = result_data["results"]
      end
    end

The reason is that this might not work well if we're sending to multiple recipients. For example, if we are sending to 100 recipients, of which 1 of them produces an error. If we raise an exception in this case, this could result in unexpected behaviour.

Particularly, if we use a background worker such as sidekiq, sidekiq will attempt to retry the execution and we might end up resending the same email to those 99 recipients again.

So my suggestion is that we don't raise an exception and instead we recommend developers to use SparkPost's webhook (https://support.sparkpost.com/customer/portal/articles/1929974-defining-webhooks) to handle errors asynchronously.

Thanks

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.